Railway Operation Simulator  v2.22.0
A railway simulator for Windows
TrainUnit.cpp
Go to the documentation of this file.
1 // TrainUnit.cpp
2 /*
3  BEWARE OF COMMENTS in .cpp files: they were accurate when written but have
4  sometimes been overtaken by changes and not updated
5  Comments in .h files are believed to be accurate and up to date
6 
7  This is a source code file for "railway.exe", a railway operation
8  simulator, written originally in Borland C++ Builder 4 Professional with
9  later updates in Embarcadero C++Builder.
10  Copyright (C) 2010 Albert Ball [original development]
11 
12  This program is free software: you can redistribute it and/or modify
13  it under the terms of the GNU General Public License as published by
14  the Free Software Foundation, either version 3 of the License, or
15  (at your option) any later version.
16 
17  This program is distributed in the hope that it will be useful,
18  but WITHOUT ANY WARRANTY; without even the implied warranty of
19  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20  GNU General Public License for more details.
21 
22  You should have received a copy of the GNU General Public License
23  along with this program. If not, see <http://www.gnu.org/licenses/>.
24 */
25 // ---------------------------------------------------------------------------
26 #include <Classes.hpp>
27 #include <Controls.hpp>
28 #include <StdCtrls.hpp>
29 #include <Forms.hpp>
30 #include <Buttons.hpp>
31 #include <ExtCtrls.hpp>
32 #include <Menus.hpp>
33 #include <Dialogs.hpp>
34 #include <Graphics.hpp>
35 #include <ComCtrls.hpp>
36 #include <fstream>
37 #include <vector>
38 #include <algorithm> //for sort
39 #include <vcl.h>
40 #include <stdlib.h> //for rand() & random()
41 #include <math.hpp> //for speed & performance calcs
42 
43 #pragma hdrstop
44 
45 #include "TrainUnit.h"
46 #include "TrackUnit.h"
47 #include "TextUnit.h" //for displaying train service reference
48 #include "GraphicUnit.h"
49 //#include "DisplayUnit.h" included in TrackUnit.h
50 #include "PerfLogUnit.h"
51 #include "Utilities.h"
52 
53 // ---------------------------------------------------------------------------
54 #pragma package(smart_init)
55 
57 
58 // ---------------------------------------------------------------------------
59 
60 int TTrain::NextTrainID = 0; // has to be initialised outside the class
61 
62 // ---------------------------------------------------------------------------
63 
64 TExitInfo::TExitInfo() //default constructor
65 {
66  ServiceReference = " ";
67  RepeatNumber = 0;
68  TimeToExitSecs = -1;
69 }
70 
71 // ---------------------------------------------------------------------------
72 
73 TTrain::TTrain(int Caller, int RearStartElementIn, int RearStartExitPosIn, AnsiString InputCode, int StartSpeedIn, int MassIn, double MaxRunningSpeedIn,
74  double MaxBrakeRateIn, double PowerAtRailIn, TTrainMode TrainModeIn, TTrainDataEntry *TrainDataEntryPtrIn, int RepeatNumberIn, int IncrementalMinutesIn,
75  int IncrementalDigitsIn, int SignallerMaxSpeedIn) : RearStartElement(RearStartElementIn), RearStartExitPos(RearStartExitPosIn), HeadCode(InputCode),
76  StartSpeed(StartSpeedIn), Mass(MassIn), MaxRunningSpeed(MaxRunningSpeedIn), MaxBrakeRate(MaxBrakeRateIn), PowerAtRail(PowerAtRailIn),
77  TrainMode(TrainModeIn), TrainDataEntryPtr(TrainDataEntryPtrIn), RepeatNumber(RepeatNumberIn), IncrementalMinutes(IncrementalMinutesIn),
78  IncrementalDigits(IncrementalDigitsIn), SignallerMaxSpeed(SignallerMaxSpeedIn)
79 /*
80  Construct a new train with general default values and input values for position and headcode.
81  Create the frontcode, headcode and background graphics here but don't delete them in a destructor.
82  This is because trains are kept in a vector and vectors erase elements during internal operations.
83  Deletion is explicit by using a special function. Increment the static class member NextTrainID
84  after setting this train's ID.
85 */
86 
87 {
88  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TTrain," + AnsiString(RearStartElementIn) + "," +
89  AnsiString(RearStartExitPosIn) + "," + AnsiString(InputCode) + "," + AnsiString(StartSpeedIn) + "," + AnsiString(MassIn) + "," +
90  AnsiString(TrainModeIn));
91  // AutoControl = true;//all trains start in auto control
92  UpdateCounter = 0;
93  TimeTimeLocArrived = false;
94  Derailed = false;
95  DerailPending = false;
96  Crashed = false;
97  StoppedAtBuffers = false;
98  StoppedAtSignal = false;
99  StoppedAtLocation = false;
100  StoppedAfterSPAD = false;
101  StoppedWithoutPower = false; // new at v2.4.0
102  StoppedForTrainInFront = false;
103  TrainInFront = false; //new at v2.18.0
104  SignallerStoppingFlag = false;
105  SignallerStopped = false;
106  SignallerRemoved = false;
107  NotInService = false;
108  HoldAtLocationInTTMode = false;
109  AllowedToPassRedSignal = false;
110  CallingOnFlag = false;
111  BeingCalledOn = false;
112  DepartureTimeSet = false;
114  TimetableFinished = false;
115  LastActionDelayFlag = false;
116  OneLengthAccelDecel = false;
117  TrainCrashedInto = -1;
119  Plotted = false;
120  TrainGone = false;
121  SPADFlag = false;
122  FrontCodePtr = new Graphics::TBitmap;
123  FrontCodePtr->PixelFormat = pf8bit;
124  FrontCodePtr->Height = 8;
125  FrontCodePtr->Width = 8;
127  FrontCodePtr->Transparent = false;
128  AValue = sqrt(2 * PowerAtRail / Mass);
130  TerminatedMessageSent = false;
131  JoinedOtherTrainFlag = false;
133  FollowOnServiceRef = ""; //added at v2.12.0
134  TreatPassAsTimeLocDeparture = false; //added at v2.12.0
135  StepForwardFlag = false;
137  for(int x = 0; x < 4; x++)
138  {
139  HeadCodeGrPtr[x] = new Graphics::TBitmap;
140  HeadCodeGrPtr[x]->PixelFormat = pf8bit;
141  HeadCodeGrPtr[x]->Height = 8;
142  HeadCodeGrPtr[x]->Width = 8;
144  HeadCodeGrPtr[x]->Transparent = false;
145  }
146  for(int x = 0; x < 4; x++)
147  {
148  BackgroundPtr[x] = new Graphics::TBitmap;
149  BackgroundPtr[x]->PixelFormat = pf8bit;
150  BackgroundPtr[x]->Height = 8;
151  BackgroundPtr[x]->Width = 8;
153  BackgroundPtr[x]->Transparent = false;
154  }
155  for(int x = 0; x < 4; x++)
156  {
158  // set here to ensure have values
159  }
160  for(int x = 0; x < 4; x++)
161  {
162  PlotElement[x] = -1; // marker for not plotted yet
163  }
164  for(int x = 0; x < 3; x++)
165  {
166  OldZoomOutElement[x] = -1; // marker for not plotted yet
167  }
169  NextTrainID++;
170 
171  // new values added to complete initialisation of all TTrain variables
172 
173  // ActionVectorEntryPtr = &(TrainDataEntryPtr->ActionVector.at(0)); can't be initialised yet as session trains created with Null
174  // TrainDataEntryPtr, initialise in AddTrain
176  FrontElementLength = 0;
177  EntrySpeed = 0;
178  ExitSpeedHalf = 0;
179  ExitSpeedFull = 0;
180  MaxExitSpeed = 0;
181  BrakeRate = 0;
182  CoastingBrakeRate = 0.03; //added at v2.18.0
184  FirstHalfMove = true;
185  EntryTime = 0;
186  ExitTimeHalf = 0;
187  ExitTimeFull = 0;
188  ReleaseTime = 0;
189  TRSTime = 0;
190  LastActionTime = 0;
191  Straddle = MidLag;
192  LeadElement = -1;
193  LeadEntryPos = 0;
194  LeadExitPos = 0;
195  MidElement = -1;
196  MidEntryPos = 0;
197  MidExitPos = 0;
198  LagElement = -1;
199  LagEntryPos = 0;
200  LagExitPos = 0;
201  TrainFailed = false; // added at v2.4.0
202  for(int x = 0; x < 4; x++)
203  {
204  HOffset[x] = 0;
205  VOffset[x] = 0;
206  PlotEntryPos[x] = 0;
207  }
208  OpTimeToAct = 60; // default value, new at v2.2.0
209  TimeToExit = -1;
210  ExitPair.first = -1;
211  ExitPair.second = -1;
212  MinsDelayed = 0.0; // new at v2.2.0
213  FirstLaterStopRecoverableTime = 0.0; // new at v2.2.0
214  FinishJoinLogSent = false;
215  // added at v2.4.0 to prevent repeatdly logging the event
218  // added at v2.4.0, no need to include in session file as will only be sent once & better that way
222  ZeroPowerNoCDTMessage = false;
227  ZeroPowerDepartMessage = false;
228  TrainInFrontMessage = false;
229  TrainFailurePending = false;
230  SkippedDeparture = false;
231  ActionsSkippedFlag = false;
232  SkipPtrValue = 0;
233  TrainSkippedEvents = 0;
234  DelayedRandMins = 0; //added at v2.13.0
235  NewDelay = 0; //added at v2.13.0
236  CumulativeDelayedRandMinsOneTrain = 0; //added at v2.13.0
237  ActualArrivalTime = TDateTime(0); //added at v2.13.0
238  LastSigPassedFailed = false; //added at v2.13.0
239  LongServRefEnteredFlag = false;
240  LongServRefNameBitmap = new Graphics::TBitmap; //added at v2.22.0 these are for displaying long serv refs above the train
241  LongServRefNameBitmap->PixelFormat = pf8bit;
242  LongServRefNameBitmap->Height = 13; //needs to be 13 so text can be entered, though top of text is 3 pixels below the top
243  LongServRefNameBitmap->Width = 54;
245 
246  LongServRefWorkingBitmap = new Graphics::TBitmap; //added at v2.22.0 these are for displaying long serv refs above the train
247  LongServRefWorkingBitmap->PixelFormat = pf8bit;
248  LongServRefWorkingBitmap->Height = 10; //shorter than name bitmap as top 3 pixels not occupied by text
249  LongServRefWorkingBitmap->Width = 54;
251 
252  ImageLongServRefBitmap = new Graphics::TBitmap; //added at v2.22.0 these are for displaying long serv refs on the operating image
253  ImageLongServRefBitmap->PixelFormat = pf8bit;
254  ImageLongServRefBitmap->Height = 10; //shorter than name bitmap as top 3 pixels not occupied by text
255  ImageLongServRefBitmap->Width = 54;
257  Utilities->CallLogPop(648);
258 }
259 
260 // ---------------------------------------------------------------------------
261 
262 void TTrain::DeleteTrain(int Caller)
263 /*
264  Delete train heap objects (bitmaps) explicitly by this special function rather than by a destructor, because vectors
265  erase elements during internal operations & if TTrain had an explicit destructor that deleted the heap elements then
266  it would be called when a vector element was erased. Calling the default TTrain destructor doesn't matter because all that
267  does is release the memory of the members (including pointers to the bitmaps), it doesn't destroy the bitmaps themselves.
268  It's important therefore to call this function before erasing the vector element, otherwise the pointers to the bitmaps
269  would be lost and the bitmaps never destroyed, thereby causing memory leaks.
270  No need to delete HeadCodePosition as that just points to existing bitmaps
271 */{
272  // if(NoDelete) return;//used when a TTrain is created to hold copied values from elsewhere
273  TrainController->LogEvent("" + AnsiString(Caller) + ",DeleteTrain," + HeadCode);
274  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",DeleteTrain," + HeadCode);
275  if(Display->ZoomOutFlag)
276  {
278  }
279  if(FrontCodePtr == 0)
280  {
281  throw Exception("Error in attempting to delete FrontCodePtr");
282  }
283  delete FrontCodePtr;
284  FrontCodePtr = 0;
285  for(int x = 0; x < 4; x++)
286  {
287  if(BackgroundPtr[x] == 0)
288  {
289  throw Exception("Error in attempting to delete BackgroundPtr[" + AnsiString(x) + "]");
290  }
291  delete BackgroundPtr[x];
292  BackgroundPtr[x] = 0;
293  }
294  for(int x = 0; x < 4; x++)
295  {
296  if(HeadCodeGrPtr[x] == 0)
297  {
298  throw Exception("Error in attempting to delete HeadCodeGrPtr[" + AnsiString(x) + "]");
299  }
300  delete HeadCodeGrPtr[x];
301  HeadCodeGrPtr[x] = 0;
302  }
303  delete LongServRefNameBitmap;
305  delete ImageLongServRefBitmap;
309  Utilities->CallLogPop(649);
310 }
311 
312 // ---------------------------------------------------------------------------
313 
315 /*
316  Plots the train starting position on screen. Note that the check for starting on straight points &
317  on wrongly set points is carried out in TrainControllerUnit [but have to allow for starting on points because
318  ChangeDirection calls this function]. Train starts on Lead & Mid elements & Straddle = LeadMid unless
319  entering at a continuation in which case Straddle = MidLag & train not plotted immediately.
320  Set the headcode graphics pointers from the headcode text, then check whether starting at a
321  continuation. If so set Mid & Lag elements to -1 so they won't be plotted, and set Lead values
322  for the continuation element. Otherwise set Lead and Mid values,
323 
324  and Lead element value unless
325  Mid element is a buffer or continuation. Set Straddle, then for the Mid element set the graphic
326  offsets and headcode positions and front code. Pick up background bitmaps for the Mid element,
327  then check if a train on either Mid or Lag and if so give a warning message and return false so
328  that the calling function can delete the train. Plot the Mid element train values then do similarly
329  for the Lag element - set offsets, pick up background bitmaps, and plot the rear two segments of
330  the train. Finally set the Plotted flag and return true.
331 */{
332  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotStartPosition," + HeadCode);
333  int NextElementPosition, NextEntryPos, ElementLength, SpeedLimit;
334 
336  // PlotStartTime = TrainController->TTClockTime;
337  FirstHalfMove = true;
338 
339  // if enter at continuation then don't plot anything at start, but set TrainIDOnElement for continuation entry so as to
340  // 'claim' it for this train to prevent any other waiting trains trying to enter
342  {
343  LagElement = -1; // not to be plotted
344  LagExitPos = 0; // not to be plotted
345  LagEntryPos = 0; // not to be plotted
346  MidElement = -1; // not to be plotted
347  MidExitPos = 0; // not to be plotted
348  MidEntryPos = 0; // not to be plotted
350  LeadExitPos = 1; // will be 1 for continuation entry
351  LeadEntryPos = 0;
352 
354  MaxExitSpeed = StartSpeed; // initial value
356  ElementLength = Track->TrackElementAt(164, LeadElement).Length01;
357  SpeedLimit = Track->TrackElementAt(165, LeadElement).SpeedLimit01;
358  if(EntrySpeed > SpeedLimit)
359  {
360  EntrySpeed = SpeedLimit;
361  }
363  {
365  }
367  // LeadElement is the element to be entered
368 
369  // Precautionary check - If need to brake EntrySpeed may be too high, so set it to the speed at which
370  // can achieve ExitSpeedFull at the half braking rate.
372  {
373  double TempEntrySpeed = sqrt((MaxExitSpeed * MaxExitSpeed) + (3.6 * 3.6 * MaxBrakeRate * ElementLength)); // half braking
374  if(TempEntrySpeed < EntrySpeed)
375  {
376  EntrySpeed = TempEntrySpeed;
378  }
379  }
380  Straddle = MidLag; // only for starting on a continuation
382  // no need to stop gap flashing if start on continuation
383  }
384  else // not starting at a continuation
385  {
386  LagElement = -1;
387  LagEntryPos = 0;
388  LagExitPos = 0;
395 
397  MaxExitSpeed = StartSpeed; // initial value
399  bool TempDerail = false; // dummy
400  NextElementPosition = Track->TrackElementAt(168, LeadElement).Conn[Track->GetAnyElementOppositeLinkPos(2, LeadElement, LeadEntryPos, TempDerail)];
402  if((PowerAtRail < 1) && EntrySpeed < 1) // added at v2.4.0
403  {
404  StoppedWithoutPower = true;
405  }
406  // facing buffers check - ignore starting speed if start facing buffers
407  StoppedAtBuffers = false;
408  // need to set here as well as in UpdateTrain() in case paused during signaller change direction
411  {
412  FrontElementSpeedLimit = Track->TrackElementAt(494, LeadElement).SpeedLimit01; // use 01 for convenience, not used
413  FrontElementLength = Track->TrackElementAt(495, LeadElement).Length01; // use 01 for convenience, not used
414  EntrySpeed = 0;
415  ExitSpeedHalf = 0;
416  ExitSpeedFull = 0;
417  MaxExitSpeed = 0;
418  // SetTrainMovementValues not called so set this here
419  BrakeRate = 0;
422  StoppedAtSignal = false;
423  // new v2.2.0: can't be at buffers and signal! If was set then won't be reset as later
424  // signal check is an 'else'
425  if(!StoppedAtLocation)
426  {
427  StoppedAtBuffers = true; // stopped at location takes precedence
428  }
429  }
430 
431  // facing continuation check - don't allow to stop even if no power
433  {
434  FrontElementSpeedLimit = Track->TrackElementAt(509, LeadElement).SpeedLimit01; // use 01 for convenience, not used
435  FrontElementLength = Track->TrackElementAt(510, LeadElement).Length01; // use 01 for convenience, not used
439  BrakeRate = 0;
440  ExitTimeHalf = TrainController->TTClockTime + TDateTime(1.8 * (double) FrontElementLength / EntrySpeed / 86400);
441  ExitTimeFull = TrainController->TTClockTime + TDateTime(3.6 * (double) FrontElementLength / EntrySpeed / 86400);
442  }
443 
444  // Signal check
445  else if((NextElementPosition > -1) && (NextEntryPos > -1))
446  // condition check added as precaution after SloughIECC error reported by James U
447  {
448  if((Track->TrackElementAt(170, NextElementPosition).Config[Track->GetNonPointsOppositeLinkPos(NextEntryPos)] == Signal) &&
449  (Track->TrackElementAt(171, NextElementPosition).Attribute == 0) && !StoppedWithoutPower)
450  {
451  FrontElementSpeedLimit = Track->TrackElementAt(172, LeadElement).SpeedLimit01; // use 01 for convenience, not used
452  FrontElementLength = Track->TrackElementAt(173, LeadElement).Length01; // use 01 for convenience, not used
453  EntrySpeed = 0;
454  ExitSpeedHalf = 0;
455  ExitSpeedFull = 0;
456  MaxExitSpeed = 0;
457  BrakeRate = 0;
460  if(!StoppedAtLocation) //if it is stopped at location then don't want StoppedAtSignal until departure time if still red then, & UpdateTrain takes care of thet
461  {
462  StoppedAtSignal = true;
464  // TrainController->LogActionError(39, HeadCode, "", SignalHold, Track->TrackElementAt(754, NextElementPosition).ElementID);
465  }
467  {
468  // set both StoppedAtLocation & StoppedAtSignal, so that 'pass stop signal' is offered in popup menu rather than move
469  // forwards, but don't change the background colour so still shows as stopped at location
470  StoppedAtSignal = true;
471  }
472  }
473  else
474  {
475  StoppedAtSignal = false;
476  if(NextEntryPos > 1)
477  {
478  ElementLength = Track->TrackElementAt(174, NextElementPosition).Length23;
479  SpeedLimit = Track->TrackElementAt(175, NextElementPosition).SpeedLimit23;
480  }
481  else
482  {
483  ElementLength = Track->TrackElementAt(176, NextElementPosition).Length01;
484  SpeedLimit = Track->TrackElementAt(177, NextElementPosition).SpeedLimit01;
485  }
486  if(EntrySpeed > SpeedLimit)
487  {
488  EntrySpeed = SpeedLimit;
489  }
491  {
493  }
495  TDateTime TestTime = TrainController->TTClockTime; // test
496  AnsiString TimeString = Utilities->Format96HHMMSS(TestTime); // test
497  SetTrainMovementValues(2, NextElementPosition, NextEntryPos);
498  // NextElement is the element to be entered
499 
500  // Precautionary check - If need to brake EntrySpeed may be too high, so set it to the speed at which
501  // can achieve ExitSpeedFull at the half braking rate.
503  {
504  double TempEntrySpeed = sqrt((MaxExitSpeed * MaxExitSpeed) + (3.6 * 3.6 * MaxBrakeRate * ElementLength));
505  // half braking
506  if(TempEntrySpeed < EntrySpeed)
507  {
508  EntrySpeed = TempEntrySpeed;
509  SetTrainMovementValues(3, NextElementPosition, NextEntryPos);
510  }
511  }
512  }
513  }
515  {
516  throw Exception("Error, LeadElement Exit Connection is NotSet");
517  }
518  }
519  if(MidElement > -1) // will be -1 if start on continuation
520  {
521  Straddle = LeadMid;
525  {
526  for(int x = 0; x < 4; x++)
527  {
528  HeadCodePosition[x] = HeadCodeGrPtr[3 - x];
529  }
530  }
531  else
532  {
533  for(int x = 0; x < 4; x++)
534  {
536  }
537  }
538  if(TrainMode == Timetable)
539  {
541  }
542  else
543  {
545  }
547  // pick up background bitmaps [0] & [1] & plot HeadCodes [0] & [1]
548 
551 /* Move check to AddTrain, also, now that can start on bridges need to check that other train is on same track before refusing
552  if((Track->TrackElementAt(182, LeadElement).TrainIDOnElement > -1) || ((MidElement > -1) && (Track->TrackElementAt(183, MidElement).TrainIDOnElement > -1)))
553  {
554  ShowMessage("Can't place train " + HeadCode + "; another train already present at location");
555  Utilities->CallLogPop(651);
556  return false;
557  }
558 */
563  PlotTrainGraphic(8, 0, Display);
564  PlotTrainGraphic(9, 1, Display);
565 
568 
569  // pick up background bitmaps [2] & [3]
570 
573 
574  PlotElement[2] = MidElement;
576  PlotElement[3] = MidElement;
578  PlotTrainGraphic(10, 2, Display);
579  PlotTrainGraphic(11, 3, Display);
580  // Plotted = true; set in PlotTrainGraphic
581  //below handles long serv ref display
583  }
584  Display->Update(); // resurrected when Update() dropped from PlotOutput etc
585  Utilities->CallLogPop(652);
586 }
587 
588 // ---------------------------------------------------------------------------
589 void TTrain::UnplotTrain(int Caller)
590 {
591  // Note: If trouble is experienced with the PlotAlternativeTrackRouteGraphic functions remove them & test for train on a bridge and if so call Clearand..
592  if(!Plotted)
593  {
594  return;
595  }
596  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",UnplotTrain," + HeadCode);
597 
598  if(Straddle == MidLag)
599  {
600  if(MidElement > -1)
601  {
606  // to force plot of locked route marker, needed once only for the element
607  }
608  if(LagElement > -1)
609  {
614  // to force plot of locked route marker, needed once only for the element
615  }
616  }
617  else if(Straddle == LeadMidLag)
618  {
619  if(LeadElement > -1)
620  {
623  // to force plot of locked route marker, needed once only for the element
624  }
625  if(MidElement > -1)
626  {
631  // to force plot of locked route marker, needed once only for the element
632  }
633  if(LagElement > -1)
634  {
637  // to force plot of locked route marker, needed once only for the element
638  }
639  }
640  else if(Straddle == LeadMid)
641  {
642  if(LeadElement > -1)
643  {
648  // to force plot of locked route marker, needed once only for the element
649  }
650  if(MidElement > -1)
651  {
656  // to force plot of locked route marker, needed once only for the element
657  }
658  }
659  if(LeadElement > -1)
660  {
662  }
663  if(MidElement > -1)
664  {
666  }
667  if(LagElement > -1)
668  {
670  }
671  Plotted = false;
674  {
676  }
677  Display->Update();
678  // without this the screen 'blinks' at next Clearand... prob forces a full repaint for some reason
679  // resurrected when Update() dropped from PlotOutput etc
680  Utilities->CallLogPop(653);
681 }
682 
683 // ----------------------------------------------------------------------------
684 
685 void TTrain::UpdateTrain(int Caller)
686 /*
687  Note: Some changes made since comments written
688 
689  Brief:
690  Enter with Straddle defining train position wrt Lag, Mid & Lead elements. Is only MidLag at this point
691  on first entry at a continuation (with no train plotted), in all other cases it is either LeadMid (when train fully
692  on Lead & Mid elements) or LeadMidLag (when train straddling 3 elements).
693  Thereafter on entry Straddle = LeadMidLag or LeadMid; LeadMid if train fully on Mid & Lead elements, and
694  LeadMidLag if on Lag, Mid and Lead elements (back on lag, front on Lead, & middle 2 segments on Mid).
695  If enter with Straddle = LeadMid, then train is in effect in the first half of the next element, and moves half onto it after
696  the half time point has been passed. The values for the next element were set when the train was last updated when Straddle became
697  LeadMid from LeadMidLag. After the half time point has been passed Straddle is
698  changed to MidLag within the function and all elements moved down one, old Mid becomes
699  the new lag, old Lead becomes the new Mid, and a new Lead is obtained. Then the new positions are plotted, and finally Straddle is
700  incremented to reflect the position the train now occupies.
701 
702  Detail:
703  Set TrainFailurePending if all conditions met
704  Check whether stopped at a non-red signal, and if so reset StoppedAtSignal so train can move.
705  Check whether buffers at immediate exit, either when first enter the function or later, and set StoppedAtBuffers if so
706  and return.
707  If Straddle == LeadMid then train fully on Lead and Mid, so ready for a major update:-
708  If there's a LagElement (there will be but include check for good practice - next
709  function depends on it) Check whether DerailPending set - set during last GetLeadElement if appropriate but only acted on here when
710  train fully on offending point - Derail set and DerailPanding reset, train background
711  colour changed (note that BackgroundColour is a property of the train itself) then return.
712  If no derail pending reset Lag and Mid elements to the old Mid and Lead values, reset Straddle to MidLag, then set
713  the new LeadElement, which will be the next connected element (obtain using GetLeadElement) or -1 if the current
714  LeadElement is an exit continuation. During GetLeadElement the element at LeadElement is checked and if a stop
715  signal is found StoppedAtSignal is set to true, otherwise StoppedAtSignal is set to false. Also Derail is set
716  if LeadElement is a fouled trailing point.
717  Now, the train is moved on by one segment. Firstly the last BackgroundElement is set to LagElement, then the last
718  segment of the LagElement is unplotted (if there is a LagElement - may be entering at a continuation), by
719  replotting the last background segment and checking whether the element is a bridge or crossover with the other
720  track in a route, in which case the route colour is replotted.
721  Then, if Straddle == LeadMidLag (train will move completely off the element during this function), and the train
722  track is in a route, then all the train elements are removed from the route unless it's an autosig route. Normally only the
723  LeadElement will be in a route for a moving train, but when originally placed all elements may be in the route so check them all.
724  Note also that there may be two routes at a given element position, but only one of them is the correct one, so this
725  is identified prior to the removal. Also the TrainIDs are reset because the train will be fully off this element at the end of
726  the function. If Straddle == LeadMidlag and the element being left is a ContinuationExit the the TrainGone flag is set so the
727  train can be deleted by the calling function, and the function returns.
728  If the element is a signal in the train movement direction, then it is reset to red (Attribute = 0) and is replotted
729  to show the red aspect. Finally if element is a signal in the other direction it is replotted as it was - need to
730  plot individually because could have any aspect, the background bitmap that was picked up earlier contains just the
731  basic red aspect.
732 
733  Now all the array values are updated, but the [0] values are as yet invalid, these have to be obtained explicitly from
734  the new LeadElement later. The headcode graphics are updated so that it reads correctly - left to right & top to bottom,
735  regardless of direction, and with the correct front code colour.
736 
737  The new front segment background bitmap is now picked up and the graphic offsets set, and the segments are plotted.
738  No more unplotting is needed as all but the last segment are overwritten by later segments, and the new front
739  segment is just plotted, though the background bitmap at that location has to be picked up. Just where they are
740  plotted depends on the Straddle value, [0] is always on Lead, [1] is on Lead if Straddle == LeadMidLag or Mid if
741  Straddle == MidLag; [2] is always on Mid, and [3] is on Mid if Straddle == LeadMidLag or Lag if Straddle == MidLag.
742  Also prior to plotting the lead segment a crash check is made, and if true the Crashed flag is set and the
743  TrainCrashedInto value also set to the current TrainID - this is so it too becomes crashed and hence stopped.
744 
745  The Crashed flag is now checked, and if set the front headcode colour is changed to the same as the rest of the code,
746  and the background colour changed. Then the train that is crashed into is also set to Crashed, and its colours
747  changed similarly. The function then returns.
748 
749  If Crashed is not set then Straddle is incremented and the function returns.
750 */
751 
752 {
753  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",UpdateTrain," + HeadCode);
754  UpdateCounter++;
755  // 100 counts = 5secs (used in splits to prevent too frequent length checks in front & rear splits)
756  if(UpdateCounter >= 100)
757  {
758  UpdateCounter = 0;
759  }
760  int RandRange = (TrainController->MTBFHours * 3600) / 53;
761 
762  // MTBFHours is in timetable clock hours, min value is 1 & max value is 9,999 (integer values on input)
763  // but double on use because it represents timetable clock time, so at 1/16 speed RandRange is * 16 (160,000 max) & at 16x speed its /16 (1/16 min)
764  // i.e MTBFHours is Input value/TTClockSpeed (conversion is done in InterfaceUnit)
765  if(int(TrainController->RandomFailureCounter) == (rand() % 1060))
766  // RandomFailureCounter value is fixed for a full cycle of train updates so this
767  // makes sure there's no bunching of failures as there is for a fixed comparison number
768  // or a small range of comparison numbers. True every 53 secs (real time) on average rand()
769  // gives a random number between 0 and 16384 (defined as RAND_MAX in stdlib.h)
770  {
771  if(!TrainFailed && !TrainOnContinuation(0) && (RandRange > 0) && (PowerAtRail > 1) && !((TrainMode == Timetable) && TimetableFinished)
772  && !Crashed && !Derailed && !((TrainMode == Signaller) && Stopped()))
773  // RandomFailureCounter resets to 0 every 53 secs, if RandRange is 0 then no failure rate is set - i.e. failure rate = 0
774  // don't fail if:
775  // (a) on a continuation (entering or leaving);
776  // (b) already failed;
777  // (c) power effectively zero (8000) min value for powered; 0.08 for 'no power';
778  // (d) train terminated;
779  // (e) crashed or derailed; or
780  // (f) under signaller control and stopped.
781  // (g) TreatPassAsTimeLocDeparture is true //added at v2.12.0
782  {
783  if((random(RandRange) == 0) && !TreatPassAsTimeLocDeparture)
784  // max value for RandRange is over 2x10^9
785  {
786  // here if failure due
787  TrainFailurePending = true;
788  // the failure occurs when PlotElements set to proper Lead & Mid Elements
789  }
790  }
791  }
792 /* dropped as it allows a train to stop on a half element - when reach (if Stopped()) at line 1310
793  if ((PowerAtRail < 1) && (EntrySpeed < 1)) // added at v2.4.0
794  {
795  StoppedWithoutPower = true;
796  }
797 */
798 // float TimeToExit; //added at v2.10.0 Removed these so original values retained - used when train on continuation
799 // THVShortPair ExitPair; //added at v2.10.0
800  int LockedVectorNumber;
801  Graphics::TBitmap *EXGraphicPtr = RailGraphics->bmTransparentBgnd;
802  // default values - these needed for route checker below
803  Graphics::TBitmap *EntryDirectionGraphicPtr = RailGraphics->bmTransparentBgnd;
804 
806  {
808  }
809  if(Crashed || Derailed)
810  {
812  {
813  PlotTrain(7, Display);
814  // replotted every cycle because of level crossing crashes, otherwise a flashing level crossing wipes out half of the train
815  Display->Update();
816  }
817  OpTimeToAct = 0.0;
818  // need to set this here as wouldn't be calculated otherwise as return from UpdateTrain
819  Utilities->CallLogPop(1017);
820  return; // no further action, user has to remove or work around
821  }
822 
824  {
826  }
828  {
830  }
831  if(StoppedWithoutPower && (TrainMode == Signaller)) //added at v2.13.2 as this condition not covered - was shown as normal
832  {
834  }
836  // introduced at v1.2.0, formerly 'TimeTimeLocArrived = false' was included
837  // in the next condition 'if(!Stopped() && !SPADFlag)' which led to repeated arrival messages if signaller control allowed a train
838  // to move & then stop again at the same station
839  {
840  TimeTimeLocArrived = false;
841  }
842  if(!Stopped() && !SPADFlag && !TrainFailed)
843  {
845  }
846  // set or release StoppedAtBuffers if fully on 2 elements depending on LeadElement
847  // Note that if LeadElement == Buffers train must be facing the buffer so no need to check orientation
848 /* old version where force a stop at buffers regardless of speed
849  if((Straddle == LeadMid) && (LeadElement > -1) && (Track->TrackElementAt(, LeadElement).TrackType == Buffers)) StoppedAtBuffers = true;
850  else StoppedAtBuffers = false;
851 */
852 
853  // new version where crash if run into buffers
854  if(!Crashed)
855  {
856  if((Straddle == LeadMid) && (LeadElement > -1) && (Track->TrackElementAt(602, LeadElement).TrackType == Buffers))
857  {
858  if(ExitSpeedFull > 1)
859  {
860  Crashed = true;
864  // SendMissedActionLogs(3, -1, ActionVectorEntryPtr);//-1 is a marker for send messages for all remaining entries, including Fer if present
865  // no need for missed action logs - will be sent when train removed
866  StoppedAtBuffers = false;
867  }
869  // stopped at location & stopped without power take precedence
870  {
871  StoppedAtBuffers = true;
872  }
873  else
874  {
875  StoppedAtBuffers = false;
876  }
877  }
878  else
879  {
880  StoppedAtBuffers = false;
881  }
882  }
883  else
884  {
885  StoppedAtBuffers = false;
886  }
887  // if crashed don't want stopped at buffers set
888 
889  // also crash if run into a level crossing that is changing or has barriers up
890  if(!Crashed)
891  {
892  if((Straddle == LeadMid) && (LeadElement > -1) && (ExitSpeedFull > 1))
893  {
894  int H = Track->TrackElementAt(873, LeadElement).HLoc;
895  int V = Track->TrackElementAt(874, LeadElement).VLoc;
896  if(Track->IsLCAtHV(40, H, V) && !Track->IsLCBarrierDownAtHV(2, H, V))
897  {
898  Crashed = true;
902  // no need for missed action logs - will be sent when train removed
903  }
904  }
905  }
907  {
909  }
910  // set or reset HoldAtLocationInTTMode (if true then actions are needed before train departs)
912  //if Command == "" then either TimeLoc or TimeTimeLoc so don't hold, and added last part at v2.12.0 so don't hold if have both command == pas and Treat... flag
913  {
914  HoldAtLocationInTTMode = true;
915  }
916  else if(TrainMode == Timetable)
917  {
918  HoldAtLocationInTTMode = false;
919  }
920  // in Signaller mode HoldAtLocationInTTMode not changed
921 
922  // check if departure pending & set times unless already set
923  if(TrainMode == Timetable)
924  {
926  // && !StoppedAtBuffers) - drop this, set times whether or not at buffers
927  {
928  if((ActionVectorEntryPtr->Command != "pas") && (ActionVectorEntryPtr->DepartureTime > TDateTime(-1)) && (ActualArrivalTime > TDateTime(0)))
929  {
930  AnsiString ReasonArray[24] = {"a driver is awaited","a guard is awaited","of a medical emergency","of a technical problem","of a security issue",
931  "of a safety issue","of a disturbance","a train crew member has been taken ill","the driver has been taken ill","the guard has been taken ill",
932  "a report has been received concerning safety","a shoe has been lost under the train","of a reported theft",
933  "of an incident involving an animal","some luggage has been lost under the train","a minor repair is needed","a suspicious object has to be dealt with safely",
934  "a door is stuck open","additional stock has to be attached","a security alert","of a train fault","of an operating incident","safety checks are required",
935  "of a shortage of on train crew"};
936  //(ActionVectorEntryPtr->Command != "pas") added at v2.13.0 to rule out passes, though probably not needed
937  //(ActualArrivalTime > TDateTime(0)) added at v2.13.0 to ensure that it has been set and to dismiss trains that are present
938  //at start or have no departure time set.
939  NewDelay = 0; //section relating to random delays added at v2.13.0
940  TDateTime TimetableReleaseTime = TrainController->GetRepeatTime(0, ActionVectorEntryPtr->DepartureTime, RepeatNumber, IncrementalMinutes); //Timetable value
941  TDateTime DwellTime = TimetableReleaseTime - ActualArrivalTime; //Timetable value
942  if(DwellTime < TDateTime(30.0 / 86400))
943  {
944  DwellTime = TDateTime(30.0 / 86400);
945  }
946  int randval = random(10000);
947  if(randval != 0) //if randval == 0 or DelayMode == Nil then NewDelay will be 0 as set above
948  {
949  if(Utilities->DelayMode == Minor)
950  {
951  if(randval < Utilities->MinorDelayCutoff)
952  {
953  NewDelay = Utilities->MinorDelayFactor * log(Utilities->MinorDelayCutoff/randval);//minutes (confusingly log in C++Builder gives the natural logarithm)
954  }
955  }
956  else if(Utilities->DelayMode == Moderate)
957  {
958  if(randval < Utilities->ModerateDelayCutoff)
959  {
961  }
962  }
963  else if(Utilities->DelayMode == Major)
964  {
965  if(randval < Utilities->MajorDelayCutoff)
966  {
968  }
969  }
970  }
971 //NewDelay = 25; //test
972  if(double(TrainController->TTClockTime) <= (Utilities->LastDelayTTClockTime + 5.0/1440.0)) //if within 5 mins of last delay for any train
973  { //then don't delay. Added at v2.13.0
974  NewDelay = 0;
975  }
976  if(NewDelay < 1)
977  {
978  NewDelay = 0;
979  }
980  if(NewDelay < double(DwellTime) * 1440) //if less than scheduled dwell time then no additional delay
981  {
982  NewDelay = 0;
983  }
984  else
985  {
986  NewDelay -= double(DwellTime) * 1440;//reduce delay by dwell time
987  }
988  if(DelayedRandMins > 0)
989  {
990  DelayedRandMins -= double(DwellTime) * 1440;//reduce knock-on random delay by dwell time
991  }
992  if(DelayedRandMins < 0)
993  {
994  DelayedRandMins = 0;//can't be less than zero
995  }
997  {
998  NewDelay -= DelayedRandMins; //NewDelay is the additional delay over and above the existing knock-on delay (from earlier random delays)
999  //the formula above already includes knock-on effects
1000  DelayedRandMins += NewDelay; //the new total delay, knock-on + additional
1001 // CumulativeDelayedRandMinsOneTrain += DelayedRandMins; //don't add here, add when depart, else this value can be > late mins
1002  }
1003  else
1004  {
1005  NewDelay = 0;
1006 // CumulativeDelayedRandMinsOneTrain += DelayedRandMins; //as above
1007  }
1008  ReleaseTime = LastActionTime + TDateTime(NewDelay / 1440); //earliest possible release time
1009  if(NewDelay < 0.5) //less than the 30 secs min interval
1010  {
1011  ReleaseTime = LastActionTime + TDateTime(30.0 / 86400);
1012  }
1013  if(ReleaseTime < TimetableReleaseTime)
1014  {
1015  ReleaseTime = TimetableReleaseTime; //back to correct time
1016  NewDelay = 0;
1017  DelayedRandMins = 0;
1018  }
1019  if(DelayedRandMins > double(ReleaseTime - TimetableReleaseTime) * 1440)
1020  {
1021  DelayedRandMins = double(ReleaseTime - TimetableReleaseTime) * 1440; //reduce this if time has been made up
1022  }
1023 
1024  if(DelayedRandMins < NewDelay) //may be if reduced above, but if so need to reduce NewDelay also
1025  {
1027  }
1028  //may be possible to simplify all the above but as it seems to work ok leave as is
1029  if(int(NewDelay) > 0) //additional delay over and above knock-on effects from earlier random delays
1030  {
1032  if(int(NewDelay) == 1)
1033  {
1034  Display->WarningLog(12, Utilities->Format96HHMMSS(TrainController->TTClockTime) + ": " + HeadCode + " delayed at " +
1035  ActionVectorEntryPtr->LocationName + " by 1 minute");
1036  PerfLogForm->PerformanceLog(18, Utilities->Format96HHMMSS(TrainController->TTClockTime) + " WARNING: " + HeadCode + " delayed at " +
1037  ActionVectorEntryPtr->LocationName + " by 1 minute because of a minor technical issue");
1038  TrainController->StopTTClockMessage(140, HeadCode + " delayed at " +
1039  ActionVectorEntryPtr->LocationName + " by 1 minute because of a minor technical issue");
1040  }
1041  else
1042  {
1043  Display->WarningLog(11, Utilities->Format96HHMMSS(TrainController->TTClockTime) + ": " + HeadCode + " delayed at " +
1044  ActionVectorEntryPtr->LocationName + " by " + AnsiString(int(NewDelay)) + " minutes");
1045  if(NewDelay >= 10) //give variable reasons for >= 10 mins
1046  {
1047  int randval2 = rand() % 24; //24 reasons
1048  AnsiString Reason = ReasonArray[randval2];
1050  HeadCode + " delayed at " + ActionVectorEntryPtr->LocationName + " by " + AnsiString(int(NewDelay)) +
1051  " minutes because " + Reason);
1052  TrainController->StopTTClockMessage(141, HeadCode + " delayed at " + ActionVectorEntryPtr->LocationName + " by " + AnsiString(int(NewDelay)) +
1053  " minutes because " + Reason);
1054  }
1055  else
1056  {
1058  HeadCode + " delayed at " + ActionVectorEntryPtr->LocationName + " by " + AnsiString(int(NewDelay)) +
1059  " minutes because of a minor problem");
1060  TrainController->StopTTClockMessage(142, HeadCode + " delayed at " + ActionVectorEntryPtr->LocationName + " by " + AnsiString(int(NewDelay)) +
1061  " minutes because of a minor problem");
1062  }
1063  }
1064  }
1065  TRSTime = ReleaseTime - TDateTime(10.0 / 86400);
1066  ActualArrivalTime = TDateTime(0); //only run through this section once per arrival
1067  DepartureTimeSet = true;
1068  }
1069  else if(ActionVectorEntryPtr->DepartureTime > TDateTime(-1)) //as was, for trains that don't have an errival time set
1070  {//if have skipped to a new service then DepartureTime will be set (in above segement when earlier train arrived)
1071  //but ArrivalTime won't be set as it is reset to 0 at end of above segement when earlier train arrived, so this segement
1072  //will run without any random delays which might cause additional complications from mixing modifications and best avoided.
1073  NewDelay = 0;
1074  DelayedRandMins = 0;
1076  if(ReleaseTime <= LastActionTime + TDateTime(30.0 / 86400))
1077  {
1078  ReleaseTime = LastActionTime + TDateTime(30.0 / 86400);
1079  }
1080  TRSTime = ReleaseTime - TDateTime(10.0 / 86400);
1081  DepartureTimeSet = true;
1082  }
1083  else if((ActionVectorEntryPtr->Command == "pas") && TreatPassAsTimeLocDeparture) //new segment at v2.12.0 to treat a pass as a departure
1084  {//for when skip to a new service at a pass location. As above this also avoids any random delays, and will avoid above segment because
1085  //departure time isn't set - it's an event time. Again random delays in this situation might cause additional complications
1086  //from mixing modifications so best avoided.
1087  NewDelay = 0;
1088  DelayedRandMins = 0;
1090  if(ReleaseTime <= LastActionTime + TDateTime(30.0 / 86400))
1091  {
1092  ReleaseTime = LastActionTime + TDateTime(30.0 / 86400);
1093  }
1094  TRSTime = ReleaseTime - TDateTime(10.0 / 86400);
1095  DepartureTimeSet = true;
1096  }
1097  }
1098  }
1099  if(TrainController->OpTimeToActUpdateCounter == 0)// && TrainController->OpActionPanelVisible) removed last condition so always calc TimeToExit
1100  {
1101  OpTimeToAct = CalcTimeToAct(0, TimeToExit, ExitPair); // called after ReleaseTime set
1102 // this->TimeToExit = TimeToExit; don't need these as values updated directly
1103 // this->ExitPair = ExitPair;
1104  // calculate every 1 sec (in real time, not timetable time) for all trains
1105  }
1106  // check if being held at location pending any actions & deal with them if time appropriate & >= 30s since LastActionTime
1107  if(TrainMode == Timetable)
1108  {
1109  if((ActionVectorEntryPtr->Command != "Frh") && (ActionVectorEntryPtr->Command != "Frh-sh"))
1110  {
1111  RemainHereLogNotSent = true;
1112  }
1114  {
1115  // ignore TimeLoc & TTLoc departures
1116  // Action logs given in functions
1118  LastActionTime + TDateTime(30.0 / 86400)))
1119  {
1120  if(ActionVectorEntryPtr->Command == "fsp")
1121  {
1122  // added for v1.3.2 because when add new train to TrainVector 'this' address likely invalidated, hence make no more changes to
1123  //'this' train. Next clock cycle will deal with any required changes
1124  FrontTrainSplit(0);
1125  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1126  { //this is checked before each return
1127  TrainHasFailed(0);
1128  }
1129  Utilities->CallLogPop(2041);
1130  return;
1131  }
1132  else if(ActionVectorEntryPtr->Command == "rsp")
1133  {
1134  // added for v1.3.2 because when add new train to TrainVector 'this' address likely invalidated, hence make no more changes to
1135  //'this' train. Next clock cycle will deal with any required changes
1136  RearTrainSplit(0);
1137  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1138  {
1139  TrainHasFailed(1);
1140  }
1141  Utilities->CallLogPop(2042);
1142  return;
1143  }
1144  else if(ActionVectorEntryPtr->Command == "Fjo")
1145  {
1146  FinishJoin(0);
1147  }
1148  else if(ActionVectorEntryPtr->Command == "jbo")
1149  {
1150  JoinedBy(0);
1151  }
1152  else if(ActionVectorEntryPtr->Command == "cdt")
1153  {
1154  ChangeTrainDirection(0, false);
1155  }
1156  else if(ActionVectorEntryPtr->Command == "dsc")
1157  {
1158  Description = ActionVectorEntryPtr->NewDescription; //changed at v2.16.1
1162  }
1163  else if(ActionVectorEntryPtr->Command == "cms") //added at v2.21.0 for change of max speed
1164  {
1169  }
1170  else if(ActionVectorEntryPtr->Command == "Fns")
1171  {
1172  NewTrainService(0, false);
1173  }
1174  else if(ActionVectorEntryPtr->Command == "Frh")
1175  {
1176  RemainHere(0);
1177  }
1178  else if(ActionVectorEntryPtr->Command == "Fer")
1179  {
1180  TimetableFinished = true;
1181  }
1182  // other aspects of 'Fer' dealt with in TTrain::HasTrainGone()
1183  else if(ActionVectorEntryPtr->Command == "F-nshs")
1184  {
1186  }
1187  else if(ActionVectorEntryPtr->Command == "Frh-sh")
1188  {
1189  RepeatShuttleOrRemainHere(0, false);
1190  }
1191  else if(ActionVectorEntryPtr->Command == "Fns-sh")
1192  {
1194  }
1195 /*
1196  F-nshs (FNSShuttle) = Finish New Service (Shuttle) = finish, form new shuttle service in same direction, details =
1197  shuttle headcode (no train creation)
1198  Frh-sh (TimeCmdHeadCode) = Finish then restart as a shuttle using Snt-sh or Sns-sh, when all shuttle repeats done
1199  remain here
1200  Fns-sh (FSHNewService) = Finish then restart as a shuttle using Snt-sh or Sns-sh, when all shuttle repeats done
1201  form new service via Sns-fsh using the NonRepeatingShuttleLinkHeadCode
1202 */
1203  }
1204  }
1205  else
1206  {
1208  {
1210  }
1211  }
1212  }
1213  if(TrainMode == Timetable)
1214  {
1215  if(StoppedAtBuffers)
1216  {
1217  // error if buffers (& element before it) not at a location, or if buffer location different to ActionVectorEntryPtr location
1218  // if buffer location same as ActionVectorEntryPtr location & not Frh then error will be given for inability to depart
1219  AnsiString BufferLocation = Track->TrackElementAt(604, LeadElement).ActiveTrackElementName;
1220  if(BufferLocation == "")
1221  {
1223  }
1224  AnsiString ExpectedLocation = ActionVectorEntryPtr->LocationName;
1225  if((BufferLocation == "") || (BufferLocation != ExpectedLocation))
1226  {
1230  {
1232  // SendMissedActionLogs(-1, ActionVectorEntryPtr);//-1 is a marker for send messages for all remaining entries, including Fer if present
1233  // Drop missed actions so user can still use sig mode to get back on track
1235  }
1236  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1237  {
1239  TrainHasFailed(2);
1240  }
1241  Utilities->CallLogPop(1020);
1242  return;
1243  }
1244  else if((BufferLocation != "") && (BufferLocation == ExpectedLocation) && DepartureTimeSet && !RevisedStoppedAtLoc() && (TrainController->TTClockTime >
1245  ReleaseTime))
1246  {
1249  {
1252  // SendMissedActionLogs(-1, ActionVectorEntryPtr);//-1 is a marker for send messages for all remaining entries, including Fer if present
1253  // Drop missed actions so user can still use sig mode to get back on track
1255  }
1256  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1257  {
1259  TrainHasFailed(3);
1260  }
1261  Utilities->CallLogPop(1397);
1262  return;
1263  }
1264  }
1265  else
1266  {
1268  }
1269  }
1270  else
1271  {
1273  }
1274  if(TrainMode == Timetable)
1275  {
1277  {
1279  }
1281  {
1283  }
1284  }
1285  // Pick up element next to the train front (if exists) to check for calling-on, restart after a cleared signal, or
1286  // restart after stopped for train in front
1287  int NextElementPosition, NextEntryPos;
1288 
1289  if(LeadElement > -1) // if an exit continuation then not set
1290  {
1291  if((Track->TrackElementAt(186, LeadElement).TrackType != Points) || ((LeadEntryPos != 0) && (LeadEntryPos != 2)))
1292  {
1294  }
1295  else if((Track->TrackElementAt(187, LeadElement).TrackType == Points) && ((LeadEntryPos == 0) || (LeadEntryPos == 2)))
1296  {
1297  if(Track->TrackElementAt(188, LeadElement).Attribute == 0)
1298  {
1299  LeadExitPos = 1;
1300  }
1301  else
1302  {
1303  LeadExitPos = 3;
1304  }
1305  }
1306  NextElementPosition = Track->TrackElementAt(189, LeadElement).Conn[LeadExitPos];
1307  NextEntryPos = Track->TrackElementAt(190, LeadElement).ConnLinkPos[LeadExitPos];
1308  }
1309  else
1310  {
1311  NextElementPosition = -1;
1312  NextEntryPos = -1;
1313  }
1314  if((NextElementPosition > -1) && (NextEntryPos > -1))
1315  // may be buffers or continuation so need this check
1316  {
1317 /*
1318  Check whether calling-on conditions met:-
1319  a) approaching train has stopped at a signal but not at a location;
1320  b) if there is a facing train at the station, it is being held appropriately (must be awaiting a join (Fjo or jbo) or a
1321  change of direction (cdt), remaining here (Frh), or under signaller control);
1322  c) at least 1 platform available for the approaching train;
1323  d) points (if any) set for direct route into platform;
1324  e) approaching train is to stop at station;
1325  f) no more facing signals between train and platform;
1326  g) [dropped g]
1327  h) train in front preventing route being set far enough to release stop signal;
1328  i) train in front not exiting at continuation;
1329  j) signal must be within 4km of the stop platform;
1330  k) [dropped (k), now can set a reoute or part route into platform so can set points more easily];
1331  l) no existing route conflicts with the route into the platform; and
1332  m) not failed or without power (these added at v2.10.0)
1333  If all OK & route or part route not already set then set an unrestricted route into the station (just to the first platform), to prevent point changing or
1334  other route conflicts - if a partial route set than can still change points outside the route or have a route conflict if another route is set.
1335 */
1336  if(TrainMode == Timetable)
1337  {
1338  if(CallingOnAllowed(0)) //returns false if failed or no power (modified afer v2.9.2)
1339  {
1340  CallingOnFlag = true;
1341  PlotTrainWithNewBackgroundColour(1, clCallOnBackground, Display); // calling-on background
1342  }
1343  else
1344  {
1345  if(CallingOnFlag) //TrainHasFailed sets this flag to false (at v2.10.0)
1346  {
1347  if(!TrainFailed) //shouldn't be needed but include for safety at v2.10.0
1348  {
1350  }
1351  }
1352  CallingOnFlag = false;
1353  }
1354  }
1355  if(StoppedAtSignal && ((Track->TrackElementAt(191, NextElementPosition).Attribute > 0) || AllowedToPassRedSignal) && !TrainFailed && !RevisedStoppedAtLoc())
1356  {
1357  //'&& !StoppedAtLocation' added at v2.7.0 as if had been stopped at signal before tt control restored then background colour changed to normal when signal changed from red
1358  // reset PassRedSignal when reached half-way point in next element, if reset here then SetTrainMovementValues
1359  // sets StoppedAtSignal again & train doesn't move
1360  StoppedAtSignal = false;
1361  // need to recalculate exit times since old entry time expired. Straddle now at MidLag with front of train on MidElement
1362  // hence use MidElement for the calculation so same as would have been used if signal not red, when Straddle was
1363  // LeadMidLag and front of train was on LeadElement (after the current move)
1365  EntrySpeed = 0;
1367  FirstHalfMove = true;
1368  SetTrainMovementValues(4, NextElementPosition, NextEntryPos);
1369  // NextElement is the element to be entered
1370  }
1371  if((LeadElement > -1) && (LeadExitPos > -1))//this section added at v2.18.0
1372  {
1373  int NextPos = Track->TrackElementAt(649, LeadElement).Conn[LeadExitPos];
1374  if(NextPos > -1)
1375  {
1376  int NextEntryPos = Track->TrackElementAt(1674, LeadElement).ConnLinkPos[LeadExitPos];
1377  if(Track->OtherTrainOnTrack(1, NextPos, NextEntryPos, TrainID))
1378  // true if another train on NextEntryPos track whether bridge or not
1379  {
1380  TrainInFront = true;
1381  }
1382  else
1383  {
1384  TrainInFront = false;
1385  }
1386  }
1387  }
1389  {
1390  if(ClearToNextSignal(0))
1391  {
1392  StoppedForTrainInFront = false;
1393  TrainInFront = false;
1394  BeingCalledOn = false;
1395  EntrySpeed = 0;
1397  FirstHalfMove = true;
1398  SetTrainMovementValues(16, NextElementPosition, NextEntryPos);
1399  }
1400  }
1401  }
1402  if(Stopped() && TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1403  {
1404  TrainHasFailed(4);
1405  Utilities->CallLogPop(1097);
1406  return;
1407  }
1408  if((Straddle == MidLag) && (LeadElement != -1))
1409  // later check only for Straddle == LeadMid, so need this check here for initial train start
1410  {
1412  }
1413 /* Logic below as follows: This check is made to allow a restart if had StoppedAtLocation or StoppedForTrainInFront or
1414  both but potentially able to restart (i.e. not at buffers, not crashed, not derailed, not held at location, departure
1415  time due, no train in front now & no other stop condition). Note that can be StoppedForTrainInFront when not at a
1416  location since this is set in SetTrainMovementValues whenever a train has zero EntrySpeed and there is a train in front,
1417  which could be when start as Snt.
1418  If StoppedForTrainInFront but not StoppedAtLocation then need to set TRSTime high so pink not plotted, and ReleaseTime
1419  low so can restart if appropriate. BeingCalledOn was set so that when train stopped at a station it wouldn't restart
1420  until the line was clear of trains up to the next signal. Hence check whether BeingCalledOn & if so set
1421  StoppedForTrainInFront, this ensures two things - that the restart check is carried out at each cycle and also that
1422  a restart won't happen until the line is clear to the next signal, regardless of whether or not the ReleaseTime has been
1423  reached.
1424  Then check if TRS time reached & change background to pink if so, & check if release time reached & if so change
1425  background to white and clear StoppedAtLocation. Then make check of station name, and recheck StoppedForTrainInFront,
1426  if it's set check if ClearToNextSignal and if so clear StoppedForTrainInFront & BeingCalledOn. If not ClearToNextSignal
1427  then return. If either not StoppedForTrainInFront or ClearToNextSignal then restart, calling SetTrainMovementValues &
1428  sending a message to the performancelog.
1429 */
1430 
1431  if(TrainMode == Timetable)
1432  {
1434  {
1435 // if(BeingCalledOn) //dropped when added TrainInFront at v2.18.0
1436 // {
1437 // TrainInFront = true;
1438 // }
1439  SetTrainMovementValues(25, LeadElement, LeadEntryPos); //this is purely to set StoppedForTrainInFront as needed before formal call //added at v2.19.1
1440  if((TrainController->TTClockTime >= TRSTime) && (PowerAtRail >= 1) && !StoppedForTrainInFront) //added later conditions at v2.19.1
1441  {
1443  }
1444  else //added at v2.14.0 as if a train ready to depart (pink b'gnd) taken under sig control then restored to tt control b'gnd stayed pink,
1445  { //even though release time now 30 seconds after tt control restored
1447  }
1449  {
1450  // value updated at every scheduled departure & arrival
1451  if((PowerAtRail < 1) && EntrySpeed < 1) // added at v2.4.0 moved here from after 'StoppedAtLocation = false' at v2.19.1
1452  {
1453  StoppedWithoutPower = true;
1454  }
1455  if(!StoppedWithoutPower && !StoppedForTrainInFront) //added at v2.19.1, don't change colour if no power or train in front
1456  {
1458  }
1459  AnsiString StationName;
1461  {
1463  }
1465  {
1467  }
1468  else
1469  {
1470  throw Exception("Error - Stopped at through station but neither lead nor mid elements have a name");
1471  }
1472  EntrySpeed = 0;
1474  if(!StoppedWithoutPower && !StoppedForTrainInFront) //added at v2.19.1 & all below put in conditional block - due to JasonB false departure log in email of 21/02/24
1475  { //= extended it to StoppedForTrainInFront as well for same reasons
1476  ZeroPowerDepartMessage = false;
1477  TrainInFrontMessage = false;
1478  FirstHalfMove = true;
1479  StoppedAtLocation = false;
1480  if((NextElementPosition > -1) && (NextEntryPos > -1))
1481  // condition check added for SloughIECC error reported by James U
1482  {
1483  if((Track->TrackElementAt(720, NextElementPosition).Config[Track->GetNonPointsOppositeLinkPos(NextEntryPos)] == Signal) &&
1484  (Track->TrackElementAt(721, NextElementPosition).Attribute == 0))
1485  {
1486  StoppedAtSignal = true;
1488  // TrainController->LogActionError(40, HeadCode, "", SignalHold, Track->TrackElementAt(755, NextElementPosition).ElementID);
1489  }
1490  }
1492  {
1493  TimeTimeLocArrived = false;
1494  LogAction(27, HeadCode, "", Depart, StationName, "", ActionVectorEntryPtr->DepartureTime, false);
1495  // no warning for TimeTimeLoc departure
1496  }
1497  else if(TreatPassAsTimeLocDeparture) //added at v2.12.0 so late/early/on time mins recorded accurately
1498  {
1499  LogAction(36, HeadCode, "", Depart, StationName, "", ActionVectorEntryPtr->EventTime, ActionVectorEntryPtr->Warning); //EventTime because the real event is a pass
1500  }
1501  else //must be TimeLoc departure
1502  {
1504  }
1505  TreatPassAsTimeLocDeparture = false; //added at v2.12.0, reset after train departs
1506  DepartureTimeSet = false;
1507  // no need to set LastActionTime for a departure
1508  //deal here with departure pointer change, increment if SkippedDeparture
1509  CumulativeDelayedRandMinsOneTrain += DelayedRandMins; //only add these after late mins added (in LogAction)
1510 
1511  if(SkippedDeparture) //only deal with this when have power
1512  {
1515  TrainSkippedEvents = 0;
1516  SkippedDeparture = false;
1517  SkipPtrValue = 0;
1518  ActionsSkippedFlag = false;
1519  }
1520  else
1521  {
1523  }
1524  // advance pointer beyond departure action - (this line (& LogAction) used to be at the end - see
1525  // note
1526 /*
1527  Note: If train stops at station after call on with a TimeTimeLoc loaded, and before the normal stop point, then when
1528  SetTrainMovementValues called it assumes a stop at the stop point because the ActionVectorEntryPtr points to a name
1529  when NameInTimetableBeforeCDT is called and the stop positions are valid. So next element train movement is based on
1530  this calculation. However, when the departure time check is made (it is during this function when SetTrainMovementValues
1531  is called), the ActionVectorEntryPtr is advanced at the end past the departure location, so at the next element when
1532  SetTrainMovementValues is called again, all is normal, i.e. the train doesn't stop again at the location. But to cure
1533  the problem move the ActionVectorEntryPtr increment to before SetTrainMovementValues.
1534 */
1536  {
1537  StoppedAtBuffers = true;
1538  }
1539  else
1540  // if buffers or no power, don't set values
1541  {
1543  {
1544  SetTrainMovementValues(12, NextElementPosition, NextEntryPos);
1545  // NextElement is the element to be entered
1546  }
1547  else
1548  {
1550  // use LeadElement for an exit continuation
1551  }
1552  }
1553  }
1554  else if(StoppedForTrainInFront) // StoppedForTrainInFront, don't advance AVPtr - added at v2.19.1
1555  {
1556  if(!TrainInFrontMessage)
1557  {
1558  TrainController->LogActionError(67, HeadCode, "", FailTrainInFront, StationName);
1559  TrainInFrontMessage = true;
1560  }
1561  }
1562  else //no power, don't advance AVPtr - added at v2.19.1
1563  {
1565  {
1567  ZeroPowerDepartMessage = true;
1568  }
1569  }
1570 
1571  }
1572  }
1573  }
1574  if(Straddle == LeadMidLag) //train on a half element
1575  {
1577  {
1578  Utilities->CallLogPop(654);
1579  return;
1580  }
1581  }
1582  else //train fully on 2 elements
1583  {
1585  {
1586  Utilities->CallLogPop(655);
1587  return;
1588  }
1589  }
1590  if((LeadElement > -1) && (MidElement > -1))
1591  {
1593  {
1594  // don't allow to stop if exiting at a continuation as causes problems if try to change direction
1595  // if entering at continuation & LeadElement is a continuation then MidElement will be -1
1596  //don't need to check for MidElement being continuation because popup menu won't show when exiting at continuation so SignallerStoppingFlag can't be set
1597  SignallerStoppingFlag = false;
1598  StepForwardFlag = false;
1599  }
1600  }
1601  if(Stopped())
1602  // this is what prevents another movement if the train is stopped
1603  {
1604  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1605  {
1606  TrainHasFailed(5);
1607  }
1608  BrakeRate = 0;
1609  Utilities->CallLogPop(656);
1610  return;
1611  }
1612 
1613  // HERE WHEN READY FOR NEXT MOVE
1614 
1615  //added at v2.10.0 to set SPADFlag if red signal immediately ahead (as it will be if in a locked route)
1616  //check if due to run past a red signal & if so set SPADFlag (SetTrainMovementValues & its SPAD check only called when arrive fully on 2 elements)
1617  if(Straddle == LeadMid) //fully on 2 elements
1618  {
1619  if(LeadElement > -1)
1620  {
1621  if(Track->TrackElementAt(1402, LeadElement).Conn[LeadExitPos] > -1)
1622  {
1624  if(TIF.TrackType == SignalPost)
1625  {
1626  int TIFEntryPos = Track->TrackElementAt(1405, LeadElement).ConnLinkPos[LeadExitPos];
1627  int TIFExitPos = 0;
1628  if(TIFEntryPos == 0)
1629  {
1630  TIFExitPos = 1;
1631  }
1632  if((TIF.Config[TIFExitPos] == Signal) && TIF.Attribute == 0 && (ExitSpeedHalf > 1) && !AllowedToPassRedSignal && !TIF.CallingOnSet) //use ExitSpeedHalf as may have been stopped at signal so entryspeed is 0
1634  {
1635  SPADFlag = true; // user has to intervene to reset & restart after spad
1636  }
1637  }
1638  }
1639  }
1640  }
1641 
1642  // check for train in front & if so stop at next access (when train fully on element next to train)
1643  if((TrainMode == Signaller) && (Straddle == LeadMidLag))
1644  // SetTrainMovementValues brakes & stops signaller mode train for a train in front using local
1645  // variable TrainInFrontInSignallerModeFlag
1646  {
1647  if((LeadElement > -1) && (LeadExitPos > -1))
1648  {
1649  int NextPos = Track->TrackElementAt(1672, LeadElement).Conn[LeadExitPos];
1650  int NextEntryPos = Track->TrackElementAt(1675, LeadElement).ConnLinkPos[LeadExitPos];
1651  if(Track->OtherTrainOnTrack(16, NextPos, NextEntryPos, TrainID))
1652  // true if another train on NextEntryPos track whether bridge or not
1653  {
1654  TrainInFront = true;
1655  }
1656  else
1657  {
1658  TrainInFront = false;
1659  }
1660  }
1661  }
1662  if((Straddle == LeadMid) && SPADFlag)
1663  // give message + plot background when ready to move half past the signal
1664  {
1665  if(NextElementPosition > -1)
1666  {
1667  if((Track->TrackElementAt(662, NextElementPosition).Config[Track->GetNonPointsOppositeLinkPos(NextEntryPos)] == Signal) &&
1668  (Track->TrackElementAt(663, NextElementPosition).Attribute == 0))
1669  {
1670  AnsiString LocID = AnsiString(Track->TrackElementAt(664, NextElementPosition).ElementID);
1672  // if goes past 2 signals then give message twice
1674  }
1675  }
1676  }
1677  if(Straddle == LeadMidLag)
1678  // During this function train moves fully onto 2 elements, Lead & Mid, so set next 2 moves from here for the element after Lead
1679  {
1680  // if SPADFlag set allow to keep moving until signal obscured before setting background colour, & stop only when ExitSpeedFull is 0
1681  if(SPADFlag)
1682  {
1683  if(ExitSpeedFull == 0)
1684  {
1685  StoppedAfterSPAD = true;
1686  // but don't want to stop until have moved fully onto element, hence stop test is before this check
1687  }
1688  }
1690  {
1691  if(ExitSpeedFull == 0)
1692  {
1693  // only reach here when will stop on LeadMid, because SetTrainMovementValues called after this (i.e. ExitSpeedFull becomes 0 if not 0 now
1694  // after this test), and Straddle == LeadMidLag so not accessed at the half-move point, hence only reached at the full move
1695  // point when the speed is 0. So, colour change won't occur until fully stopped (early in UpdateTrain()), and the log message
1696  // is sent at the right time and once only.
1697  SignallerStopped = true;
1698  // but don't want to stop until have moved fully onto element, hence stop test is before this check
1699  StepForwardFlag = false;
1700  SignallerStoppingFlag = false;
1701  TTrackElement TE;
1702  AnsiString Loc = "";
1703  bool LocNamed = false;
1704  if(LeadElement > -1)
1705  {
1706  TE = Track->TrackElementAt(782, LeadElement);
1707  if(TE.ActiveTrackElementName != "")
1708  {
1709  Loc = TE.ActiveTrackElementName;
1710  LocNamed = true;
1711  }
1712  else
1713  {
1714  Loc = "track element " + TE.ElementID;
1715  }
1716  }
1717  if((MidElement > -1) && !LocNamed)
1718  {
1719  TE = Track->TrackElementAt(783, MidElement);
1720  if(TE.ActiveTrackElementName != "")
1721  {
1722  Loc = TE.ActiveTrackElementName;
1723  LocNamed = true;
1724  }
1725  else if(Loc == "")
1726  {
1727  Loc = "track element " + TE.ElementID;
1728  }
1729  }
1730  if(Loc == "")
1731  {
1732  Loc = "outside railway";
1733  // must have stopped after left at a continuation (because both lead & mid == -1)
1734  }
1735  else
1736  {
1737  Loc = "at " + Loc;
1738  }
1739  LogAction(30, HeadCode, "", SignallerStop, Loc, "", TrainController->TTClockTime, false); // false for warning
1740  }
1741  }
1742  if(LeadElement > -1) // if an exit continuation then not set
1743  {
1744  if((Track->TrackElementAt(202, LeadElement).TrackType != Points) || ((LeadEntryPos != 0) && (LeadEntryPos != 2)))
1745  {
1747  }
1748  else if((Track->TrackElementAt(203, LeadElement).TrackType == Points) && ((LeadEntryPos == 0) || (LeadEntryPos == 2)))
1749  {
1750  if(Track->TrackElementAt(204, LeadElement).Attribute == 0)
1751  {
1752  LeadExitPos = 1;
1753  }
1754  else
1755  {
1756  LeadExitPos = 3;
1757  }
1758  }
1759  NextElementPosition = Track->TrackElementAt(205, LeadElement).Conn[LeadExitPos];
1760  NextEntryPos = Track->TrackElementAt(206, LeadElement).ConnLinkPos[LeadExitPos];
1761  }
1762  else
1763  {
1764  NextElementPosition = -1;
1765  NextEntryPos = -1;
1766  }
1769  FirstHalfMove = true; //will be when finished the move onto 2 elements during this function
1770 
1771  if((PowerAtRail < 1) && EntrySpeed < 1) // added at v2.4.0
1772  {
1773  StoppedWithoutPower = true;
1774  }
1775  if((NextElementPosition > -1) && (NextEntryPos > -1) && !SPADFlag)
1776  // may be buffers or continuation. SPADFlag added at v2.1.0
1777  // so don't override the SPAD colour & don't set StoppedAtSignal
1778  {
1779  if((Track->TrackElementAt(207, NextElementPosition).Config[Track->GetNonPointsOppositeLinkPos(NextEntryPos)] == Signal) &&
1780  (Track->TrackElementAt(208, NextElementPosition).Attribute == 0) && (ExitSpeedFull < 1) && !RevisedStoppedAtLoc())
1781  {
1782  StoppedAtSignal = true;
1783  if(!StoppedWithoutPower)
1784  // leave background as is if no power, but set StoppedAtSignal
1785  {
1787  }
1788  // TrainController->LogActionError(41, HeadCode, "", SignalHold, Track->TrackElementAt(756, NextElementPosition).ElementID);
1789  }
1790  }
1791  if(!Stopped())
1792  {
1793  if((NextElementPosition > -1) && (NextEntryPos > -1))
1794  // may be buffers or continuation (skip SetTrainMovementValues if buffers, if
1795  // a stop element that isn't buffers - e.g. station, then will skip the calcs
1796  // during SetTrainMovementValues to avoid trying to divide by zero - see that
1797  // function for fuller explanation
1798  {
1799  SetTrainMovementValues(8, NextElementPosition, NextEntryPos);
1800  // NextElement is the element to be entered
1801  }
1802  // follow the continuation exits:-
1803  else if((LeadElement > -1) && (Track->TrackElementAt(209, LeadElement).TrackType == Continuation))
1804  {
1806  // Use LeadElement for calcs if lead is a continuation
1807  }
1808  else if((MidElement > -1) && (Track->TrackElementAt(210, MidElement).TrackType == Continuation))
1809  {
1811  // Use MidElement for calcs if mid is a continuation
1812  }
1813  else if((LagElement > -1) && (Track->TrackElementAt(211, LagElement).TrackType == Continuation))
1814  {
1816  // Use LagElement for calcs if lag is a continuation
1817  }
1818  }
1819  // remove route elements if not autosigs - this section moved from below, was under LagElement > -1 condition but needs to cover LagElement == -1
1820  if((AllRoutes->GetRouteTypeAndGraphics(2, LeadElement, LeadEntryPos, EXGraphicPtr, EntryDirectionGraphicPtr) == TAllRoutes::NotAutoSigsRoute))
1821  // Trains may not be in a route
1822  // Since Straddle = LeadMidLag at this point the train is going to move fully off the existing Lag & fully onto existing Lead element during this function
1823  {
1824  // NB if LeadElement == -1 then the above test returns NoRoute
1825  int TempH = Track->TrackElementAt(213, LeadElement).HLoc;
1826  int TempV = Track->TrackElementAt(214, LeadElement).VLoc;
1827  int TempELink = Track->TrackElementAt(215, LeadElement).Link[LeadEntryPos];
1828  TAllRoutes::TRouteElementPair FirstPair, SecondPair;
1829  FirstPair = AllRoutes->GetRouteElementDataFromRoute2MultiMap(10, TempH, TempV, SecondPair);
1830  if((FirstPair.first > -1) && (AllRoutes->GetFixedRouteAt(143, FirstPair.first).GetFixedPrefDirElementAt(153,
1831  FirstPair.second).GetELink() == TempELink))
1832  {
1833  AllRoutes->RemoveRouteElement(10, TempH, TempV, TempELink);
1834  }
1835  else if((SecondPair.first > -1) && (AllRoutes->GetFixedRouteAt(144, SecondPair.first).GetFixedPrefDirElementAt(154,
1836  SecondPair.second).GetELink() == TempELink))
1837  {
1838  AllRoutes->RemoveRouteElement(11, TempH, TempV, TempELink);
1839  }
1840  }
1841  if(AllRoutes->GetRouteTypeAndGraphics(3, MidElement, MidEntryPos, EXGraphicPtr, EntryDirectionGraphicPtr) == TAllRoutes::NotAutoSigsRoute)
1842  // Trains may not be in a route
1843  {
1844  int TempH = Track->TrackElementAt(216, MidElement).HLoc;
1845  int TempV = Track->TrackElementAt(217, MidElement).VLoc;
1846  int TempELink = Track->TrackElementAt(218, MidElement).Link[MidEntryPos];
1847  TAllRoutes::TRouteElementPair FirstPair, SecondPair;
1848  FirstPair = AllRoutes->GetRouteElementDataFromRoute2MultiMap(11, TempH, TempV, SecondPair);
1849  if((FirstPair.first > -1) && (AllRoutes->GetFixedRouteAt(145, FirstPair.first).GetFixedPrefDirElementAt(155,
1850  FirstPair.second).GetELink() == TempELink))
1851  {
1852  AllRoutes->RemoveRouteElement(12, TempH, TempV, TempELink);
1853  }
1854  else if((SecondPair.first > -1) && (AllRoutes->GetFixedRouteAt(146, SecondPair.first).GetFixedPrefDirElementAt(156,
1855  SecondPair.second).GetELink() == TempELink))
1856  {
1857  AllRoutes->RemoveRouteElement(13, TempH, TempV, TempELink);
1858  }
1859  }
1860  if(AllRoutes->GetRouteTypeAndGraphics(4, LagElement, LagEntryPos, EXGraphicPtr, EntryDirectionGraphicPtr) == TAllRoutes::NotAutoSigsRoute)
1861  // Trains may not be in a route
1862  {
1863  int TempH = Track->TrackElementAt(219, LagElement).HLoc;
1864  int TempV = Track->TrackElementAt(220, LagElement).VLoc;
1865  int TempELink = Track->TrackElementAt(221, LagElement).Link[LagEntryPos];
1866  TAllRoutes::TRouteElementPair FirstPair, SecondPair;
1867  FirstPair = AllRoutes->GetRouteElementDataFromRoute2MultiMap(12, TempH, TempV, SecondPair);
1868  if((FirstPair.first > -1) && (AllRoutes->GetFixedRouteAt(147, FirstPair.first).GetFixedPrefDirElementAt(157,
1869  FirstPair.second).GetELink() == TempELink))
1870  {
1871  AllRoutes->RemoveRouteElement(14, TempH, TempV, TempELink);
1872  }
1873  else if((SecondPair.first > -1) && (AllRoutes->GetFixedRouteAt(148, SecondPair.first).GetFixedPrefDirElementAt(158,
1874  SecondPair.second).GetELink() == TempELink))
1875  {
1876  AllRoutes->RemoveRouteElement(15, TempH, TempV, TempELink);
1877  }
1878  AllRoutes->CheckMapAndRoutes(8); // test
1879  }
1880  if(LagElement > -1)
1881  // not entering at a continuation so can deal with train leaving the lag element
1882  {
1884  // amended below so route elements removed for the complete train (for NotAutoSigsRoutes), so train never standing on a route once it
1885  // starts moving, covers for eliminating route when train reaches buffers, and prevents odd route segments when route extended while
1886  // straddling 3 elements (formerly the last segment was replotted as a route & stayed plotted
1887 
1888  TPrefDirElement PrefDirElement;
1889  // plot locked route marker for any element if appropriate (i.e. if a locked AutoSigs route) but only when train leaves element completely
1890  // as this is a 16x16 graphic
1892  {
1894  RailGraphics->LockedRouteCancelPtr[PrefDirElement.GetELink()]);
1895  }
1896  if(ContinuationExit(2, LagElement, LagExitPos)) // true if Element is a continuation and Exitpos is the continuation end
1897  {
1898  int RouteNumber;
1899  TrainGone = true;
1900  // flag to indicate train to be deleted - outside this function
1902  {
1903  TTrainController::TContinuationAutoSigEntry ContinuationAutoSigEntry;
1904  ContinuationAutoSigEntry.RouteNumber = RouteNumber;
1905  // calc distance from & inc last signal to exit
1906  int LastElement = LagElement, LastExitPos = LagExitPos, CumDistance = 0;
1907  int NewLastElement = 0, NewLastExitPos = 0;
1908  // need above because can't change LastElement & LastExitPos until both new values obtained
1909  // while((Track->TrackElementAt(684, LastElement).Config[LastExitPos] != Signal) && (CumDistance < 1200)) as was
1910  while((Track->TrackElementAt(913, LastElement).Config[LastExitPos] != Signal) && (CumDistance < 1200) && (Track->TrackElementAt(897,
1911  LastElement).TrackType != Points))
1912  // extra condition above added because of Moric1998's error (see email of 24/03/2016), where had an autosigs route across points, and another continuation on track not occupied by route so
1913  // failed when found a new element = -1 when tried to cross the continuation. Note this routine can only deal with non points as it uses GetNonPointsOppositeLinkPos
1914  // leave CumDistance as it was in these circumstances.
1915  {
1916  if(LastExitPos < 2)
1917  {
1918  CumDistance += Track->TrackElementAt(685, LastElement).Length01;
1919  }
1920  else
1921  {
1922  CumDistance += Track->TrackElementAt(686, LastElement).Length23;
1923  }
1924  NewLastElement = Track->TrackElementAt(687, LastElement).Conn[Track->GetNonPointsOppositeLinkPos(LastExitPos)];
1925  if(NewLastElement == -1)
1926  // this will catch buffers or any other connection failure
1927  {
1928  break; //throw Exception("Error, Connection = -1 in Continuation loop in UpdateTrain"); //dropped at v2.15.0 because of Brent Mackie's error file of
1929  } //10/02/23, had two continuations linked with no signal between
1930  NewLastExitPos = Track->TrackElementAt(688, LastElement).ConnLinkPos[Track->GetNonPointsOppositeLinkPos(LastExitPos)]; //so when train exited this routine tracked
1931  if(NewLastExitPos == -1) //back to the entry continuation which had no further connection - doesn't need to be an error at all!
1932  {
1933  break; //throw Exception("Error, ConnLinkPos = -1 in Continuation loop in UpdateTrain"); //dropped at v2.15.0 because of Brent Mackie's error file of 10/02/23 , see above
1934  }
1935  LastElement = NewLastElement;
1936  LastExitPos = NewLastExitPos;
1937  }
1938  // if at signal add this in too (may not be signal if 'break;' encountered but doesn't matter)
1939  if(CumDistance < 1200)
1940  {
1941  CumDistance += Track->TrackElementAt(689, LastElement).Length01; // only need 01 for signal
1942  }
1943  // now have distance including the signal, if >=1200m use 100m (for a signal immediately after the continuation)
1944  // else use 1200m - CumDistance
1945  int FirstDistance = 0;
1946  if(CumDistance >= 1200)
1947  {
1948  FirstDistance = 100;
1949  }
1950  else
1951  {
1952  FirstDistance = 1200 - CumDistance;
1953  }
1954  if(FirstDistance < 100)
1955  {
1956  FirstDistance = 100; // don't allow < 100
1957  }
1958  // can now calc the time delays in seconds - FirstDelay, SecondDelay & ThirdDelay, these are doubles
1959  // BUT - first check whether ExitSpeedFull is very low (Mark had divide by zero error with zero exit speed using v2.4.0)
1960  if(ExitSpeedFull > 20.0)
1961  {
1962  ContinuationAutoSigEntry.FirstDelay = 3.6 * double(FirstDistance) / ExitSpeedFull;
1963  // speed in km/h & distance in m so mult by 3.6 to bring to secs
1964  ContinuationAutoSigEntry.SecondDelay = ContinuationAutoSigEntry.FirstDelay + 4320.0 / ExitSpeedFull;
1965  // 4320.0 = 3.6 * 1200, .0 to make it a double
1966  ContinuationAutoSigEntry.ThirdDelay = ContinuationAutoSigEntry.SecondDelay + 4320.0 / ExitSpeedFull;
1967  }
1968  else
1969  {
1970  ContinuationAutoSigEntry.FirstDelay = 60.0; // 60 secs between each action
1971  ContinuationAutoSigEntry.SecondDelay = 120.0;
1972  ContinuationAutoSigEntry.ThirdDelay = 180.0;
1973  }
1974  ContinuationAutoSigEntry.AccessNumber = 0;
1975  ContinuationAutoSigEntry.PassoutTime = TrainController->TTClockTime;
1977  {
1979  for(VectorIT = TrainController->ContinuationAutoSigVector.begin(); VectorIT != TrainController->ContinuationAutoSigVector.end();
1980  VectorIT++)
1981  {
1982  if(VectorIT->RouteNumber == RouteNumber)
1983  {
1984  // another train has passed out of same route so erase earlier entry
1985  TrainController->ContinuationAutoSigVector.erase(VectorIT);
1986  break;
1987  }
1988  }
1989  }
1990  TrainController->ContinuationAutoSigVector.push_back(ContinuationAutoSigEntry);
1991  }
1993  // need to plot this as returning early so will miss the later plot (not a bridge so don't need PlotAlternativeTrackRouteGraphic)
1994  Display->Update();
1995  // need to keep this since Update() not called for PlotSmallOutput as too slow
1996  Utilities->CallLogPop(659);
1997  return;
1998  }
1999  // above covers for exiting at continuation, need XLinkPos check to exclude entering at a continuation
2000  if(LeadElement > -1)
2001  {
2002  TTrackElement &TE = Track->TrackElementAt(224, LeadElement); //added at v2.13.0 for brevity
2003  if(TE.Config[LeadExitPos] == Signal)
2004  // changed to lead so reset early
2005  {
2006  LastSigPassedFailed = false; //used to cancel route elements up to next signal for autosigs route
2007  TE.Attribute = 0; // red
2008  int RouteNumber; //only used for autosigs routes
2009  //add chance to fail when train passes a signal
2010  if((random(Utilities->SignalChangeEventsPerFailure) == 0) && !TE.Failed && (Utilities->FailureMode != FNil) &&
2011  (TrainMode == Timetable) && !TE.CallingOnSet) //can't fail twice, calling on signal can't fail
2012  {
2014  IFE.TVPos = LeadElement;
2015  TE.Failed = true;
2016  Display->WarningLog(19, Utilities->Format96HHMMSS(TrainController->TTClockTime) + ": Signal failed at " + TE.ElementID);
2017  PerfLogForm->PerformanceLog(42, Utilities->Format96HHMMSS(TrainController->TTClockTime) + " WARNING: Signal failed at " + TE.ElementID);
2018  TrainController->StopTTClockMessage(129, "Signal at " + TE.ElementID +
2019  " failed when changing aspect.\nTrains can only pass under signaller control.");
2020  AllRoutes->RebuildRailwayFlag = true; //force ClearandRebuildRailway at next clock tick
2021  LastSigPassedFailed = true;
2022  //set repair time, random value in minutes between 10 and 179
2023  double FailureMinutes = double(random(Utilities->MaxRandomRepairTime) + Utilities->FixedMinRepairTime); //between 10 and 179 minutes at random
2024  TDateTime RepairTime = TrainController->TTClockTime + TDateTime(FailureMinutes / 1440);
2025  IFE.RepairTime = RepairTime;
2027  Track->FailedSignalsVector.push_back(IFE); //rearwards signals will be set when LagElement leaves signal
2028  }
2029  TE.CallingOnSet = false;
2030  // don't plot if zoomed out
2031  if(!Display->ZoomOutFlag)
2032  {
2034  }
2035  // covers signal resetting in same direction
2036  }
2037  }
2039  {
2040  AllRoutes->RebuildRailwayFlag = true; //added at v2.13.0 to replot signal after train left in case it had failed
2041  if(AllRoutes->GetRouteTypeAndGraphics(5, LagElement, LagEntryPos, EXGraphicPtr, EntryDirectionGraphicPtr) == TAllRoutes::AutoSigsRoute)
2042  {
2043  Display->PlotOutput(23, Track->TrackElementAt(227, LagElement).HLoc * 16, Track->TrackElementAt(228, LagElement).VLoc * 16, EXGraphicPtr);
2044  Display->PlotOutput(24, Track->TrackElementAt(229, LagElement).HLoc * 16, Track->TrackElementAt(230, LagElement).VLoc * 16, EntryDirectionGraphicPtr);
2045  TPrefDirElement PrefDirElement;
2046  // plot locked route marker for same side signal if appropriate (may be covered above but leave in), but only when train leaves element completely as this is a 16x16 graphic
2048  {
2050  RailGraphics->LockedRouteCancelPtr[PrefDirElement.GetELink()]);
2051  }
2053  LockedVectorNumber)))
2054  {
2056  }
2057  }
2058  }
2059  else if((LeadElement > -1) && (Track->TrackElementAt(233, LeadElement).TrackType == SignalPost))
2060  {
2061  Track->TrackElementAt(234, LeadElement).Attribute = 0; // red
2063  // don't plot if zoomed out
2064  if(!Display->ZoomOutFlag)
2065  {
2067  }
2068  // covers signal passed in opposite direction - replot as red, regardless of what it was before, though should already have been red
2069  }
2071  {
2072  if(AllRoutes->GetRouteTypeAndGraphics(6, LagElement, LagEntryPos, EXGraphicPtr, EntryDirectionGraphicPtr) == TAllRoutes::AutoSigsRoute)
2073  {
2074  Display->PlotOutput(26, Track->TrackElementAt(236, LagElement).HLoc * 16, Track->TrackElementAt(237, LagElement).VLoc * 16, EXGraphicPtr);
2075  Display->PlotOutput(27, Track->TrackElementAt(238, LagElement).HLoc * 16, Track->TrackElementAt(239, LagElement).VLoc * 16, EntryDirectionGraphicPtr);
2076  // below added at v1.3.0 to reset signals if back out of an autosigs route under signaller control after changing direction, when new LeadElement not on route (if it had
2077  // been the route would have been ForceCancelled). Note that the signal is not facing the direction of travel else would have entered
2078  // "if(Track->TrackElementAt(, LagElement).Config[LagExitPos] == Signal)" above and wouldn't be here
2079  int RouteNumber;
2081  // already know it's an autosigsroute, this is just to get the RouteNumber
2082  // addition below at v1.3.2 - found that a signal that had reached double yellow in ContinuationAutoSigs was reset to red when a following train's lag element
2083  // moved off a signal in the normal course of events. It was caused when a train backed out of an autosigs route under signaller control after changing
2084  // direction (see DevHistory.txt). Hence check that the train is in signaller mode and that the train's lead element isn't on the same route before calling SetRouteSignals.
2085  int RouteNumber2;
2087  // already know it's an autosigsroute, this is just to get the RouteNumber
2088  if((TrainMode == Signaller) && (RouteNumber2 != RouteNumber))
2089  // note that if not in a route (as likely) then RouteNumber2 set to -1
2090  {
2091  AllRoutes->GetFixedRouteAt(217, RouteNumber).SetRouteSignals(10);
2092  // this was in the 1.3.0 addition but without the condition
2093  }
2094  // end of 1.3.2 addition
2095  // end of 1.3.0.addition
2096  }
2097  TPrefDirElement PrefDirElement;
2098  // plot locked route marker for opp side signal if appropriate (may be covered above but leave in), but only when train leaves element completely as this is a 16x16 graphic (OK - Straddle == LeadMidLag)
2100  {
2102  RailGraphics->LockedRouteCancelPtr[PrefDirElement.GetELink()]);
2103  }
2104  }
2105  }
2106  }
2107  // straddle ONLY changed here, check if 'LeadMid' first & if so ready for updating Elements
2108  if(Straddle == LeadMid) //about to move half onto next element
2109  {
2110  AllowedToPassRedSignal = false;
2111  // if had been allowed to pass then at this point it will move half onto signal so can be reset
2112  // if(LagElement > -1) ResetTrainElementID(LagElement, LagEntryPos);//train fully off old LagElement so can clear TrainOnElement flags - no, reset at earlier call when lag moves off element
2113  if(DerailPending)
2114  // set during last GetLeadElement, but only act on it when train fully on offending point
2115  // i.e. next time Straddle reaches LeadMid
2116  {
2117  Derailed = true;
2118  DerailPending = false;
2122  Utilities->CallLogPop(657);
2123  return;
2124  }
2131  Straddle = MidLag;
2132  // train now fully on the updated Lag & Mid, the front segment is going to move onto the new
2133  // LeadElement during this function (note that if stopped at signal then won't get this far)
2134  if(LeadElement > -1)
2135  {
2137  // i.e an exit continuation only
2138  // if don't exclude entry continuations then can't progress past it
2139  {
2140  LeadElement = -1;
2141  }
2142  else
2143  {
2144  GetLeadElement(0);
2145  // sets or resets DerailPending & StoppedAtSignal, and sets LeadElement values
2147  if(Stopped())
2148  {
2149  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
2150  {
2151  TrainHasFailed(6);
2152  }
2153  Utilities->CallLogPop(658);
2154  return; // i.e. don't move forward one step if next element is a red signal
2155  }
2156  }
2157  }
2158  }
2159  if(LagElement > -1)
2160  {
2161  // below are the actions required at both half moves for LagElement > -1
2163 
2164  // if was in locked route but has timed out when train leaves then plot the normal track graphic over the route graphic that is
2165  // still in BackgroundGraphic[3], if wasn't in a route then will just replot the same BackgroundGraphic
2166  // need to do this for each half element
2167 
2168  TPrefDirElement PrefDirElement;
2169  if(!(AllRoutes->IsElementInLockedRouteGetPrefDirElementGetLockedVectorNumber(7, LagElement, LagExitPos, PrefDirElement, LockedVectorNumber)))
2170  {
2171  int RouteNumber; // holder for call below - not used
2173  {
2174  if(Utilities->clTransparent == TColor(0xFFFFFF))
2175  // change to black for a white background
2176  {
2178  // only applies for AutoSigs Route in case was locked & timed out
2179  }
2180  else
2181  // change to white for a dark background
2182  {
2184  // only applies for AutoSigs Route in case was locked & timed out
2185  }
2187  }
2188  }
2190  // above in case train just moving off a bridge & either alternative track in a route - need to keep its route colour,
2191  // or a train on the opposite track - needs to be replotted
2192  }
2193  // update all array values
2194  HOffset[3] = HOffset[2];
2195  HOffset[2] = HOffset[1];
2196  HOffset[1] = HOffset[0];
2197  VOffset[3] = VOffset[2];
2198  VOffset[2] = VOffset[1];
2199  VOffset[1] = VOffset[0];
2200  Graphics::TBitmap *TempPtr = BackgroundPtr[3];
2201 
2202  BackgroundPtr[3] = BackgroundPtr[2];
2203  BackgroundPtr[2] = BackgroundPtr[1];
2204  BackgroundPtr[1] = BackgroundPtr[0];
2205  BackgroundPtr[0] = TempPtr;
2206 
2207  // update headcode graphics depending on Lead entry value
2208  if(LeadElement > -1) // if Lead is -1 then stays as is
2209  {
2211  {
2212  for(int x = 0; x < 4; x++)
2213  {
2214  HeadCodePosition[x] = HeadCodeGrPtr[3 - x];
2215  }
2216  }
2217  else
2218  {
2219  for(int x = 0; x < 4; x++)
2220  {
2222  }
2223  }
2224  }
2225  if(TrainMode == Timetable)
2226  {
2228  }
2229  else
2230  {
2232  }
2234 
2235  // plot new seg [0] on Lead & [2] on Mid ([2] always on Mid)
2236  if(LeadElement > -1)
2237  {
2238  if(Straddle == MidLag)
2239  // just about to move half onto the new lead element
2240  {
2242  // pick up new background bitmap [0]
2244  int LeadElementTrainID = Track->TrackElementAt(244, LeadElement).TrainIDOnElement;
2245  if((LeadElementTrainID > -1) && (LeadElementTrainID != TrainID))
2246  // check if own ID for entry at continuation, else crashes into itself!
2247  {
2248  // OK if crossing on a bridge
2249  int OtherTrainEntryPos = TrainController->EntryPos(0, LeadElementTrainID, LeadElement);
2250  if(OtherTrainEntryPos == -1)
2251  {
2252  throw Exception("Error - OtherTrainEntryPos not set");
2253  }
2254  if((Track->TrackElementAt(246, LeadElement).TrackType != Bridge) || (LeadEntryPos == OtherTrainEntryPos) ||
2255  // LeadEntryPos for rear end crashes
2256  (LeadExitPos == OtherTrainEntryPos))
2257  // LeadExitPos for head-on crashes
2258  {
2260  Crashed = true; // only set if Straddle = MidLag
2261  CallingOnFlag = false;
2262  // in case was set, need to disable call on if call on button had been pressed
2263  }
2264  }
2265  else if(MidElement > -1) // will be -1 for continuation entries
2266  {
2267  // check if about to move onto a crossing diagonal that is occupied by another train, and if so crash
2268  int MidExitLinkNum = Track->TrackElementAt(889, MidElement).Link[MidExitPos];
2269  int MidHLoc = Track->TrackElementAt(890, MidElement).HLoc;
2270  int MidVLoc = Track->TrackElementAt(891, MidElement).VLoc;
2271  int OtherTrainID = -1;
2272  if((MidExitLinkNum == 1) || (MidExitLinkNum == 3) || (MidExitLinkNum == 7) || (MidExitLinkNum == 9))
2273  {
2274  if(Track->DiagonalFouledByTrain(0, MidHLoc, MidVLoc, MidExitLinkNum, OtherTrainID))
2275  {
2276  TrainCrashedInto = OtherTrainID;
2277  Crashed = true; // only set if Straddle = MidLag
2278  CallingOnFlag = false;
2279  // in case was set, need to disable call on if call on button had been pressed
2280  }
2281  }
2282  }
2283  }
2284  else
2285  {
2287  // pick up new background bitmap [0]
2289  }
2290  PlotElement[0] = LeadElement;
2292  PlotTrainGraphic(12, 0, Display);
2293  }
2294  if(MidElement > -1)
2295  {
2296  PlotElement[2] = MidElement;
2298  PlotTrainGraphic(1, 2, Display);
2299  }
2300  // plot the new positions for [1] & [3] graphics - [1] on Mid if Straddle = MidLag, on Lead if Straddle = LeadMidLag
2301  // [3] on Lag if Straddle = MidLag, on Mid if Straddle = LeadMidLag
2302  if(Straddle == MidLag)
2303  {
2304  if(MidElement > -1)
2305  {
2306  PlotElement[1] = MidElement;
2308  PlotTrainGraphic(2, 1, Display);
2309  }
2310  if(LagElement > -1)
2311  {
2312  PlotElement[3] = LagElement;
2314  PlotTrainGraphic(3, 3, Display);
2315  }
2316  }
2317  else // Straddle == LeadMidLag
2318  {
2319  if(LeadElement > -1)
2320  {
2321  PlotElement[1] = LeadElement;
2323  PlotTrainGraphic(4, 1, Display);
2324  }
2325  if(MidElement > -1)
2326  {
2327  PlotElement[3] = MidElement;
2329  PlotTrainGraphic(5, 3, Display);
2330  }
2331  }
2332  if(Crashed)
2333  // only reach here if crash into another train, if crash into buffers or an LC then return earlier at the if(Stopped()) test
2334  {
2339  // in case was set, need to disable call on if call on button had been pressed
2346  Straddle = LeadMidLag;
2347  // was MidLag but plotted as LeadMidLag so change Straddle accordingly
2348  Display->Update();
2349  // resurrected when Update() dropped from PlotOutput etc
2350  Utilities->CallLogPop(660);
2351  return;
2352  }
2353  // deal here with station stops & pass times after all replotting done but before Straddle changed
2354  if(TrainMode == Timetable)
2355  {
2356  if(Straddle == LeadMidLag)
2357  {
2358  if((LeadElement > -1) && (MidElement > -1) && !TimetableFinished)
2359  {
2360  // NameInTimetableBeforeCDT returns the number by which the train ActionVectorEntryPtr needs to be incremented
2361  // to point to the location arrival entry - before a change of direction
2362  AnsiString LocName = Track->TrackElementAt(249, LeadElement).ActiveTrackElementName;
2363  bool StopRequired = false;
2364  int TTVPos = NameInTimetableBeforeCDT(1, LocName, StopRequired); //excludes continuations
2365  if(TTVPos > -1) // -1 if can't find it or if name is ""
2366  {
2367  // check if at buffers (no, dropped buffer check to allow to crash into buffers) or a through station stop,
2368  // or a station where next element contains a train or a stop signal, if so
2369  // stop now, note that for 2nd check, if next element is a bridge then will have stopped by now so no need
2370  // to test the actual track the train is on since it can't be a platform
2371  TTrackElement LeadTrackElement = Track->TrackElementAt(258, LeadElement);
2372  TTrackElement NextTrackElement; // default for now
2373  bool TrainAtStopLinkPos1 = (LeadTrackElement.StationEntryStopLinkPos1 == LeadEntryPos);
2374  bool TrainAtStopLinkPos2 = (LeadTrackElement.StationEntryStopLinkPos2 == LeadEntryPos);
2375  bool TrainAtStopLinkPos3 = (LeadTrackElement.StationEntryStopLinkPos3 == LeadEntryPos);
2376  bool TrainAtStopLinkPos4 = (LeadTrackElement.StationEntryStopLinkPos4 == LeadEntryPos);
2377  bool ForwardConnection = (LeadTrackElement.Conn[LeadExitPos] > -1);
2378  int NextElementEntryPos = -1;
2379  int NextElementExitPos = -1;
2380  bool TrainOnNextElement = false;
2381  bool StopSignalAtNextElement = false;
2382  if(ForwardConnection)
2383  // if no forward connection can't derive anything from it without errors
2384  {
2385  NextTrackElement = Track->TrackElementAt(262, LeadTrackElement.Conn[LeadExitPos]);
2386  NextElementEntryPos = LeadTrackElement.ConnLinkPos[LeadExitPos];
2387  NextElementExitPos = Track->GetNonPointsOppositeLinkPos(NextElementEntryPos);
2388  // this is only for signals so no need to worry about points ambiguity
2389  TrainOnNextElement = (NextTrackElement.TrainIDOnElement > -1);
2390  StopSignalAtNextElement = ((NextTrackElement.Config[NextElementExitPos] == Signal) && (NextTrackElement.Attribute == 0));
2391  }
2392  if(TrainAtStopLinkPos1 || TrainAtStopLinkPos2 || TrainAtStopLinkPos3 || TrainAtStopLinkPos4 || (ForwardConnection && (TrainOnNextElement || StopSignalAtNextElement)))
2393  {
2394  if(TTVPos > 0)
2395  {
2397  ActionVectorEntryPtr += TTVPos;
2398  }
2399  if(StopRequired)
2400  {
2401  StoppedAtLocation = true;
2402  StoppedAtSignal = false;
2403  // may have been set earlier at line 925 so need to reset as
2404  // StoppedAtLocation takes precedence and don't want both set at same time or have flashing graphic
2405  // in zoom out mode
2406  if(!TrainFailed)
2407  {
2409  // pale green
2410  }
2412  ActualArrivalTime = TrainController->TTClockTime; //added at v2.13.0
2414  {
2415  TimeTimeLocArrived = true;
2416  // used in case of later signaller control, when need to know
2417  // whether had arrived or not, to avoid sending the arrival
2418  // message twice, see TInterface::TimetableControl1Click
2419  }
2420  }
2421  else
2422  {
2424  }
2426  {
2428  }
2429  // don't alter ActionVectorEntryPtr if at a TimeTimeLoc (& can't be anything else other than TimeLoc or PassTime after calling NameInTimetableBeforeCDT successfully)
2431  }
2432  }
2433  }
2434  }
2435  }
2436  if(Straddle == MidLag)
2437  {
2438  Straddle = LeadMidLag;
2439  FirstHalfMove = false;
2440  }
2441  else if(Straddle == LeadMidLag)
2442  {
2443  Straddle = LeadMid;
2444  FirstHalfMove = true;
2445  }
2446  else if(Straddle == LeadMid)
2447  {
2448  throw Exception("Error, Straddle shouldn't be LeadMid prior to resetting at exit from UpdateTrain");
2449  }
2450  if(TrainFailurePending) // ok, moving but PlotElements set above
2451  {
2452  TrainHasFailed(7);
2453  }
2455  Display->Update();
2456  // need to keep this since Update() not called for PlotSmallOutput as too slow
2457  Utilities->CallLogPop(661);
2458 }
2459 
2460 // ----------------------------------------------------------------------------
2461 
2463 {
2464  if((!Display->ZoomOutFlag) && (TrainDataEntryPtr->ServiceReference.Length() > 4))
2465  {
2467  {
2469  }
2470  if(Plotted && (LeadElement > -1) && Utilities->ShowLongServRefsFlag) //if not plotted yet, exiting at continuation, or show flag false then ignore
2471  {
2472  EnterLongServRefAsName(0, Disp);
2473  }
2474  }
2475 }
2476 
2477 // ----------------------------------------------------------------------------
2478 
2479 void TTrain::EnterLongServRefAsName(int Caller, TDisplay *Disp) //added at v2.22.0 to display long serv refs
2480 { //colours clB1G0R0 for white bgnd, clB3G5R5 for dark bgnds
2481 
2482 /* There are several parts to this function as follows:
2483 StaticFeaturesDisplay & StaticfeaturesScreen are created in the TInterface constructor (and destroyed in the destructor).
2484 The display is a Display object like Display and HiddenDisplay, and has HiddenDisplay assigned to it almost at the end of Clearand...
2485 just before the trains are plotted. It is used to remove the text of the long serv. ref. prior to replotting it after the train has moved.
2486 The colours for the text and the font are set in the TTrainController constructor according to the background colour.
2487 There are two small bitmaps LongServRefNameBitmap (54 x 13 pixels) and LongServRefWorkingBitmap (54 x 10 pixels), the former to hold the
2488 long serv. ref. text on a transparent background, and the latter to hold the bitmap to be overlaid on the main display both for plotting
2489 and removal. The former is taller because when text is plotted there are 3 pixels at the top above the text that aren't used, so
2490 these are omitted for overlaying on the main display.
2491 StaticFeaturesDisplay is resized whenever the form is resized, and is cleared and filled with the transparent colour whenever OperMode or
2492 RestartOperMode selected.
2493 This function (EnterLongServRefAsName) calculates the text position (HPos & VPos, saved in LongServRefTextH & LongServRefTextV) for removal, which may be a long time
2494 after plotting. DisplayOffsetH & V are static values so apply to all three displays, and are used to ensure the correct text removal
2495 graphic is picked up from the StaticFeaturesScreen - picking anything up outside this area is blank.
2496 The text of the long serv. ref. uses unique colours (for white & dark backgrounds) so these can be identified when plotting over the main
2497 display graphic using the scanline function - LongServRefNameBitmap is examined starting 3 pixels down and where a pixel uses the relevant
2498 text colour-number (using the websafe colour palette) it is transferred onto LongServRefWorkingBitmap so the name overlies anything already there,
2499 then LongServRefWorkingBitmap drawn onto the main display at the correct position (it uses draw instead of CopyRect as that respects
2500 transparency. After plotting LongServRefWorkingBitmap is loaded from the same position in StaticFeaturesDisplay, then where LongServRefNameBitmap
2501 contains a text coloured pixel a near-transparent colour is plotted onto LongServRefWorkingBitmap in its place. Where any other colour including
2502 transparent appears it is plotted without change. A near-transparent colour is used so it won't show against the existing background, the transparent
2503 colour can't be used as it would leave the text in place.
2504 */
2505 
2506  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",EnterLongServRefAsName");
2507  if(LongServRefEnteredFlag) //if already present then return
2508  {
2509  Utilities->CallLogPop(2724);
2510  return;
2511  }
2512  int VPos, HPos, TrainHLocLead, TrainHLocMid, TrainHLocLag, TrainVLocLead, TrainVLocMid, TrainVLocLag;
2513  TTrackElement TrainLead, TrainMid, TrainLag;
2514  bool ReturnEarly = false;
2515 
2516  //set all H & VPos's
2517  if(LeadElement > -1)
2518  {
2519  TrainLead = Track->TrackElementAt(1686, LeadElement);
2520  }
2521  else
2522  {
2523  ReturnEarly = true;
2524  }
2525  if(MidElement > -1)
2526  {
2527  TrainMid = Track->TrackElementAt(1687, MidElement);
2528  }
2529  else
2530  {
2531  ReturnEarly = true;
2532  }
2533  if(LagElement > -1)
2534  {
2535  TrainLag = Track->TrackElementAt(1688, LagElement);
2536  }
2537  else if(Straddle == LeadMidLag)
2538  {
2539  ReturnEarly = true;
2540  }
2541  if(ReturnEarly)
2542  {
2543  Utilities->CallLogPop(2725);
2544  return;
2545  }
2546 
2547  int LeftHAndOffset, TopVAndOffset;
2548 //get leftmost and topmost train graphic elements
2549 
2550  if(Straddle == LeadMid)
2551  {
2552  LeftHAndOffset = (TrainLead.HLoc * 16) + HOffset[0];
2553  if(((TrainLead.HLoc * 16) + HOffset[1]) < LeftHAndOffset)
2554  {
2555  LeftHAndOffset = (TrainLead.HLoc * 16) + HOffset[1];
2556  }
2557  if(((TrainMid.HLoc * 16) + HOffset[2]) < LeftHAndOffset)
2558  {
2559  LeftHAndOffset = (TrainMid.HLoc * 16) + HOffset[2];
2560  }
2561  if(((TrainMid.HLoc * 16) + HOffset[3]) < LeftHAndOffset)
2562  {
2563  LeftHAndOffset = (TrainMid.HLoc * 16) + HOffset[3];
2564  }
2565  TopVAndOffset = (TrainLead.VLoc * 16) + VOffset[0];
2566  if(((TrainLead.VLoc * 16) + VOffset[1]) < TopVAndOffset)
2567  {
2568  TopVAndOffset = (TrainLead.VLoc * 16) + VOffset[1];
2569  }
2570  if(((TrainMid.VLoc * 16) + VOffset[2]) < TopVAndOffset)
2571  {
2572  TopVAndOffset = (TrainMid.VLoc * 16) + VOffset[2];
2573  }
2574  if(((TrainMid.VLoc * 16) + VOffset[3]) < TopVAndOffset)
2575  {
2576  TopVAndOffset = (TrainMid.VLoc * 16) + VOffset[3];
2577  }
2578  HPos = LeftHAndOffset;
2579  VPos = TopVAndOffset - 10; // - 10 so plots 10 pixels above the train
2580  }
2581  else if(Straddle == LeadMidLag)
2582  {
2583  LeftHAndOffset = (TrainLead.HLoc * 16) + HOffset[0];
2584  if(((TrainMid.HLoc * 16) + HOffset[1]) < LeftHAndOffset)
2585  {
2586  LeftHAndOffset = (TrainMid.HLoc * 16) + HOffset[1];
2587  }
2588  if(((TrainMid.HLoc * 16) + HOffset[2]) < LeftHAndOffset)
2589  {
2590  LeftHAndOffset = (TrainMid.HLoc * 16) + HOffset[2];
2591  }
2592  if(((TrainLag.HLoc * 16) + HOffset[3]) < LeftHAndOffset)
2593  {
2594  LeftHAndOffset = (TrainLag.HLoc * 16) + HOffset[3];
2595  }
2596  TopVAndOffset = (TrainLead.VLoc * 16) + VOffset[0];
2597  if(((TrainMid.VLoc * 16) + VOffset[1]) < TopVAndOffset)
2598  {
2599  TopVAndOffset = (TrainMid.VLoc * 16) + VOffset[1];
2600  }
2601  if(((TrainMid.VLoc * 16) + VOffset[2]) < TopVAndOffset)
2602  {
2603  TopVAndOffset = (TrainMid.VLoc * 16) + VOffset[2];
2604  }
2605  if(((TrainLag.VLoc * 16) + VOffset[3]) < TopVAndOffset)
2606  {
2607  TopVAndOffset = (TrainLag.VLoc * 16) + VOffset[3];
2608  }
2609  HPos = LeftHAndOffset;
2610  VPos = TopVAndOffset - 10; // - 10 so plots 10 pixels above the train
2611  }
2612  else //shouldn't be anything else
2613  {
2614  Utilities->CallLogPop(2726);
2615  return;
2616  }
2617 
2618  LongServRefTextH = HPos; //have to store these so text can be erased using absolute location
2619  LongServRefTextV = VPos;
2620 
2621  //write service reference to bitmap after clearing it, can't do it in constructor as service ref not set at that stage
2622  LongServRefNameBitmap->Transparent = true;
2623  LongServRefNameBitmap->TransparentColor = Utilities->clTransparent;
2624  LongServRefNameBitmap->Canvas->Brush->Style = bsClear; // so text prints transparent
2625  LongServRefNameBitmap->Canvas->Brush->Color = Utilities->clTransparent;
2626  LongServRefNameBitmap->Canvas->FillRect(TRect(0,0,54,13)); //fill it with transparent colour
2627  LongServRefNameBitmap->Canvas->Font->Assign(TrainController->LongServRefFont); //assign all font properties
2628  LongServRefNameBitmap->Canvas->TextOut(2, 0, TrainDataEntryPtr->ServiceReference);
2629 
2630 //Display->GetImage()->Picture->Bitmap->Canvas->CopyRect(TRect(0,0,54,13), LongServRefNameBitmap->Canvas, TRect(0,0,54,13));
2631 
2632  //copy MainScreen background segment but clear it first
2633  LongServRefWorkingBitmap->Transparent = true;
2634  LongServRefWorkingBitmap->TransparentColor = Utilities->clTransparent;
2635  LongServRefWorkingBitmap->Canvas->Brush->Style = bsClear; // so text prints transparent
2636  LongServRefWorkingBitmap->Canvas->Brush->Color = Utilities->clTransparent;
2637  LongServRefWorkingBitmap->Canvas->FillRect(TRect(0,0,54,10)); //fill it with transparent colour
2638 
2639  LongServRefWorkingBitmap->Canvas->CopyRect(TRect(0,0,54,10), Disp->GetImage()->Canvas, TRect(LongServRefTextH - (Display->DisplayOffsetH * 16),
2641 
2642 //set up ImageLongServRefBitmap ready for receiving name & on white background
2643  ImageLongServRefBitmap->Transparent = true;
2644  ImageLongServRefBitmap->TransparentColor = clB5G5R5; //white
2645  ImageLongServRefBitmap->Canvas->Brush->Style = bsClear; // so text prints transparent
2646  ImageLongServRefBitmap->Canvas->Brush->Color = clB5G5R5; //white
2647  ImageLongServRefBitmap->Canvas->FillRect(TRect(0,0,54,10)); //fill it with white
2648 
2649  Byte *SLPtrIn; // pointer to the ScanLine values in LongServRefNameBitmap
2650  Byte *SLPtrOut; // pointer to the ScanLine values in LongServRefWorkingBitmap
2651  Byte *SLPtrImage; // pointer to the ScanLine values in ImageLongServRefBitmap
2652 
2653  for(int x = 3; x < 13; x++)
2654  {
2655  SLPtrIn = reinterpret_cast<Byte*>(LongServRefNameBitmap->ScanLine[x]);
2656  SLPtrOut = reinterpret_cast<Byte*>(LongServRefWorkingBitmap->ScanLine[x - 3]);
2657  SLPtrImage = reinterpret_cast<Byte*>(ImageLongServRefBitmap->ScanLine[x - 3]);
2658  for(int y = 0; y < 54; y++)
2659  {
2660  if(SLPtrIn[y] == TrainController->LongServRefFontColNumber)
2661  {
2662  SLPtrOut[y] = SLPtrIn[y];
2663  SLPtrImage[y] = 0x01; //clB1G0R0
2664  } //else do nothing
2665  }
2666  }
2667 
2668 //ImageLongServRefBitmap now set up ready to write to image if required
2669 
2670 //copy back onto MainScreen - use Draw to retain transparent pixels CopyRect doesn't accept transparent pixels
2672 
2673 //from here building a new LongServRefWorkingBitmap ready for the next removal
2674  LongServRefWorkingBitmap->Canvas->Brush->Style = bsClear; // so text prints transparent
2675  LongServRefWorkingBitmap->Canvas->Brush->Color = Utilities->clTransparent;
2676  LongServRefWorkingBitmap->Canvas->FillRect(TRect(0,0,54,10)); //fill it with transparent colour
2677  LongServRefWorkingBitmap->Canvas->CopyRect(TRect(0,0,54,10), StaticFeaturesDisplay->GetImage()->Canvas, TRect(LongServRefTextH - (Display->DisplayOffsetH * 16),
2679  LongServRefTextV - (Display->DisplayOffsetV * 16) + 10));
2680 //now just get the background pixels where overlap with text
2681 
2682 //Disp->GetImage()->Canvas->CopyRect(TRect(0,0,200,200), StaticFeaturesDisplay->GetImage()->Canvas, TRect(LongServRefTextH - (Display->DisplayOffsetH * 16),
2683 // LongServRefTextV - (Display->DisplayOffsetV * 16), LongServRefTextH - (Display->DisplayOffsetH * 16) + 200,
2684 // LongServRefTextV - (Display->DisplayOffsetV * 16) + 200));
2685 
2686  byte *SLPtrText;
2687  for(int x = 3; x < 13; x++)
2688  {
2689  SLPtrText = reinterpret_cast<Byte*>(LongServRefNameBitmap->ScanLine[x]); //this is 13 pixels deep
2690  SLPtrOut = reinterpret_cast<Byte*>(LongServRefWorkingBitmap->ScanLine[x - 3]); //this is 10 pixels deep
2691  for(int y = 0; y < 54; y++)
2692  {
2693  if(SLPtrText[y] == TrainController->LongServRefFontColNumber)
2694  {
2695  if(SLPtrOut[y] == TrainController->BgndColNumber)
2696  {
2698  } //else leave pixel as it is
2699  } //else leave pixel as it is
2700  }
2701  }
2702 
2703 //Disp->GetImage()->Canvas->CopyRect(TRect(0,210, 54,220), LongServRefWorkingBitmap->Canvas, TRect(0,0,54,10));
2704 
2705  Disp->Update();
2706  LongServRefEnteredFlag = true;
2707  Utilities->CallLogPop(2727);
2708 }
2709 
2710 // ----------------------------------------------------------------------------
2711 
2712 void TTrain::RemoveLongServRef(int Caller, AnsiString NameText, TDisplay *Disp) //added at v2.22.0 to remove long serv refs
2713 {
2714  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RemoveLongServRef," + NameText);
2715  if(!LongServRefEnteredFlag) //if already absent then return
2716  {
2717  Utilities->CallLogPop(2728);
2718  return;
2719  }
2720 
2721 //copy back onto MainScreen, but first avoid overwriting any existing leading train colours (grey [clB4G4R4], red [clB0G0R5], or blue [clB5G0R0])
2722 //Pick up the relevant section of the main display onto a new bitmap
2723  Graphics::TBitmap *TempBitmap = new Graphics::TBitmap;
2724  TempBitmap->PixelFormat = pf8bit;
2725  TempBitmap->Height = 10; //shorter than name bitmap as top 3 pixels not occupied by text
2726  TempBitmap->Width = 54;
2727  RailGraphics->SetWebSafePalette(64, TempBitmap);
2728  TempBitmap->Canvas->CopyRect(TRect(0,0,54,10), Disp->GetImage()->Canvas, TRect(LongServRefTextH - (Display->DisplayOffsetH * 16),
2730 
2731  Byte *SLPtrIn; // pointer to the ScanLine values in TempBitmap
2732  Byte *SLPtrOut; // pointer to the ScanLine values in LongServRefWorkingBitmap
2733  for(int x = 0; x < 10; x++)
2734  {
2735  SLPtrIn = reinterpret_cast<Byte*>(TempBitmap->ScanLine[x]);
2736  SLPtrOut = reinterpret_cast<Byte*>(LongServRefWorkingBitmap->ScanLine[x]);
2737  for(int y = 0; y < 54; y++)
2738  {
2739  if(SLPtrIn[y] == 0xac) //this is clB4G4R4 - grey
2740  {
2741  SLPtrOut[y] = TrainController->BgndColNumber;
2742  }
2743  else if(SLPtrIn[y] == 0xb4) //this is clB0G0R5 - red for leading character
2744  {
2745  SLPtrOut[y] = TrainController->BgndColNumber;
2746  }
2747  else if(SLPtrIn[y] == 0x05) //this is clB5G0R0 - blue for signaller control leading character
2748  {
2749  SLPtrOut[y] = TrainController->BgndColNumber;
2750  }
2751  }
2752  }
2753 
2755  Disp->Update();
2756  LongServRefEnteredFlag = false;
2757  delete TempBitmap;
2758  Utilities->CallLogPop(2729);
2759 }
2760 
2761 // ----------------------------------------------------------------------------
2762 
2763 Graphics::TBitmap *TTrain::SetOneGraphicCode(char CodeChar)
2764 {
2765  switch(CodeChar)
2766  {
2767  case '0':
2768  return(RailGraphics->Code0);
2769 
2770  case '1':
2771  return(RailGraphics->Code1);
2772 
2773  case '2':
2774  return(RailGraphics->Code2);
2775 
2776  case '3':
2777  return(RailGraphics->Code3);
2778 
2779  case '4':
2780  return(RailGraphics->Code4);
2781 
2782  case '5':
2783  return(RailGraphics->Code5);
2784 
2785  case '6':
2786  return(RailGraphics->Code6);
2787 
2788  case '7':
2789  return(RailGraphics->Code7);
2790 
2791  case '8':
2792  return(RailGraphics->Code8);
2793 
2794  case '9':
2795  return(RailGraphics->Code9);
2796 
2797  case 'A':
2798  return(RailGraphics->CodeA);
2799 
2800  case 'B':
2801  return(RailGraphics->CodeB);
2802 
2803  case 'C':
2804  return(RailGraphics->CodeC);
2805 
2806  case 'D':
2807  return(RailGraphics->CodeD);
2808 
2809  case 'E':
2810  return(RailGraphics->CodeE);
2811 
2812  case 'F':
2813  return(RailGraphics->CodeF);
2814 
2815  case 'G':
2816  return(RailGraphics->CodeG);
2817 
2818  case 'H':
2819  return(RailGraphics->CodeH);
2820 
2821  case 'I':
2822  return(RailGraphics->CodeI);
2823 
2824  case 'J':
2825  return(RailGraphics->CodeJ);
2826 
2827  case 'K':
2828  return(RailGraphics->CodeK);
2829 
2830  case 'L':
2831  return(RailGraphics->CodeL);
2832 
2833  case 'M':
2834  return(RailGraphics->CodeM);
2835 
2836  case 'N':
2837  return(RailGraphics->CodeN);
2838 
2839  case 'O':
2840  return(RailGraphics->CodeO);
2841 
2842  case 'P':
2843  return(RailGraphics->CodeP);
2844 
2845  case 'Q':
2846  return(RailGraphics->CodeQ);
2847 
2848  case 'R':
2849  return(RailGraphics->CodeR);
2850 
2851  case 'S':
2852  return(RailGraphics->CodeS);
2853 
2854  case 'T':
2855  return(RailGraphics->CodeT);
2856 
2857  case 'U':
2858  return(RailGraphics->CodeU);
2859 
2860  case 'V':
2861  return(RailGraphics->CodeV);
2862 
2863  case 'W':
2864  return(RailGraphics->CodeW);
2865 
2866  case 'X':
2867  return(RailGraphics->CodeX);
2868 
2869  case 'Y':
2870  return(RailGraphics->CodeY);
2871 
2872  case 'Z':
2873  return(RailGraphics->CodeZ);
2874 
2875  case 'a':
2876  return(RailGraphics->Code_a);
2877 
2878  case 'b':
2879  return(RailGraphics->Code_b);
2880 
2881  case 'c':
2882  return(RailGraphics->Code_c);
2883 
2884  case 'd':
2885  return(RailGraphics->Code_d);
2886 
2887  case 'e':
2888  return(RailGraphics->Code_e);
2889 
2890  case 'f':
2891  return(RailGraphics->Code_f);
2892 
2893  case 'g':
2894  return(RailGraphics->Code_g);
2895 
2896  case 'h':
2897  return(RailGraphics->Code_h);
2898 
2899  case 'i':
2900  return(RailGraphics->Code_i);
2901 
2902  case 'j':
2903  return(RailGraphics->Code_j);
2904 
2905  case 'k':
2906  return(RailGraphics->Code_k);
2907 
2908  case 'l':
2909  return(RailGraphics->Code_l);
2910 
2911  case 'm':
2912  return(RailGraphics->Code_m);
2913 
2914  case 'n':
2915  return(RailGraphics->Code_n);
2916 
2917  case 'o':
2918  return(RailGraphics->Code_o);
2919 
2920  case 'p':
2921  return(RailGraphics->Code_p);
2922 
2923  case 'q':
2924  return(RailGraphics->Code_q);
2925 
2926  case 'r':
2927  return(RailGraphics->Code_r);
2928 
2929  case 's':
2930  return(RailGraphics->Code_s);
2931 
2932  case 't':
2933  return(RailGraphics->Code_t);
2934 
2935  case 'u':
2936  return(RailGraphics->Code_u);
2937 
2938  case 'v':
2939  return(RailGraphics->Code_v);
2940 
2941  case 'w':
2942  return(RailGraphics->Code_w);
2943 
2944  case 'x':
2945  return(RailGraphics->Code_x);
2946 
2947  case 'y':
2948  return(RailGraphics->Code_y);
2949 
2950  case 'z':
2951  return(RailGraphics->Code_z);
2952 
2953  default:
2954  return(RailGraphics->TempHeadCode);
2955  }
2956 }
2957 
2958 // ----------------------------------------------------------------------------
2959 
2960 void TTrain::SetHeadCodeGraphics(int Caller, AnsiString Code)
2961 {
2962  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SetHeadCodeGraphics," + HeadCode);
2963  if(Code.Length() != 4)
2964  {
2965  TrainController->StopTTClockMessage(62, "Headcode Incorrect length");
2966  }
2967  for(int x = 1; x < 5; x++) // AnsiString indices start at 1
2968  {
2969  HeadCodeGrPtr[x - 1]->Assign(SetOneGraphicCode(Code[x]));
2970  }
2971  if(BackgroundColour != clB5G5R5)
2972  // i.e. not the basic graphic colour as loaded from resource file
2973  {
2974  for(int x = 0; x < 4; x++)
2975  {
2977  }
2978  }
2979  Utilities->CallLogPop(1484);
2980 }
2981 
2982 // ----------------------------------------------------------------------------
2983 
2984 void TTrain::GetLeadElement(int Caller)
2985 // assumes Mid & Lag already set, sets LeadElement,
2986 // LeadEntryPos, LeadExitPos & DerailPending (don't want to act on it immediately)
2987 {
2988  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetLeadElement," + HeadCode);
2989  DerailPending = false;
2993  {
2994  // attr 0=straight, - links 0 & 1 (0 = lead)
2995  // attr 1=diverging, - links 2 & 3 (2 = lead)
2996  // set appropriate next element or derail - use a subroutine & return element & bool for derail
2997  // points always have links 0 & 2 = lead, link 1 = trailing straight, link 3 = training diverging
2998 
2999  // if enter at lead, exit at whatever attr set at
3000  // if enter at lag, exit at lead, but set derail wrt attribute
3001  if((LeadEntryPos == 0) && (Track->TrackElementAt(272, LeadElement).Attribute == 0))
3002  {
3003  LeadExitPos = 1;
3004  }
3005 
3006  // strictly speaking shouldn't need to set to 0 and 2 correctly since TrackIsInARoute caters for both, but
3007  // best to be on safe side
3008  else if(LeadEntryPos == 0)
3009  {
3010  LeadEntryPos = 2;
3011  LeadExitPos = 3;
3012  }
3013  else if((LeadEntryPos == 2) && (Track->TrackElementAt(273, LeadElement).Attribute == 0))
3014  {
3015  LeadEntryPos = 0;
3016  LeadExitPos = 1;
3017  }
3018  else if(LeadEntryPos == 2)
3019  {
3020  LeadExitPos = 3;
3021  }
3022  else if((LeadEntryPos == 1) && (Track->TrackElementAt(274, LeadElement).Attribute == 0))
3023  {
3024  LeadExitPos = 0;
3025  }
3026  else if(LeadEntryPos == 1)
3027  {
3028  LeadExitPos = 0;
3029  DerailPending = true;
3030  }
3031  else if((LeadEntryPos == 3) && (Track->TrackElementAt(275, LeadElement).Attribute == 0))
3032  {
3033  LeadExitPos = 0;
3034  DerailPending = true;
3035  }
3036  else if(LeadEntryPos == 3)
3037  {
3038  LeadExitPos = 0;
3039  }
3040  }
3041  else if(LeadEntryPos == 0)
3042  {
3043  LeadExitPos = 1;
3044  }
3045  else if(LeadEntryPos == 1)
3046  {
3047  LeadExitPos = 0;
3048  }
3049  else if(LeadEntryPos == 2)
3050  {
3051  LeadExitPos = 3;
3052  }
3053  else if(LeadEntryPos == 3)
3054  {
3055  LeadExitPos = 2;
3056  }
3057  // TTrackElement TrackElement = Track->TrackElementAt(276, LeadElement);
3058 /* signal check moved to Update() function
3059  if((TrackElement.TrackType == SignalPost) && (TrackElement.Config[LeadExitPos] == Signal)
3060  && (TrackElement.Attribute == 0))//0 = red
3061  {
3062  StoppedAtSignal = true; //comment out for test of locked route graphic replot
3063  }
3064  else
3065  {
3066  StoppedAtSignal = false;
3067  }
3068 */
3069  Utilities->CallLogPop(662);
3070 }
3071 
3072 // ----------------------------------------------------------------------------
3073 
3074 void TTrain::GetOffsetValues(int Caller, int &HOffset, int &VOffset, int Link) const
3075 {
3076  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetOffsetValues," + AnsiString(Link) + "," + HeadCode);
3077  switch(Link)
3078  {
3079  case 1:
3080  {
3081  HOffset = 0;
3082  VOffset = 0;
3083  break;
3084  }
3085 
3086  case 2:
3087  {
3088  HOffset = 4;
3089  VOffset = 0;
3090  break;
3091  }
3092 
3093  case 3:
3094  {
3095  HOffset = 8;
3096  VOffset = 0;
3097  break;
3098  }
3099 
3100  case 4:
3101  {
3102  HOffset = 0;
3103  VOffset = 4;
3104  break;
3105  }
3106 
3107  case 6:
3108  {
3109  HOffset = 8;
3110  VOffset = 4;
3111  break;
3112  }
3113 
3114  case 7:
3115  {
3116  HOffset = 0;
3117  VOffset = 8;
3118  break;
3119  }
3120 
3121  case 8:
3122  {
3123  HOffset = 4;
3124  VOffset = 8;
3125  break;
3126  }
3127 
3128  case 9:
3129  {
3130  HOffset = 8;
3131  VOffset = 8;
3132  break;
3133  }
3134 
3135  default:
3136  {
3137  throw Exception("Error in GetOffsetValues - Link value wrong");
3138  }
3139  }
3140  Utilities->CallLogPop(674);
3141 }
3142 
3143 // ---------------------------------------------------------------------------
3144 
3145 bool TTrain::LowEntryValue(int EntryLink) const
3146 {
3147 /* returns true if EntryLink is 1, 2, 4 or 7, in these circumstances the front of the train (i.e.
3148  the character that is red or blue) is the last character of the headcode, otherwise it's the first character of the headcode
3149 */
3150  if((EntryLink == 1) || (EntryLink == 2) || (EntryLink == 4) || (EntryLink == 7))
3151  {
3152  return(true);
3153  }
3154  else
3155  {
3156  return(false);
3157  }
3158 }
3159 
3160 // ---------------------------------------------------------------------------
3161 
3162 void TTrain::PickUpBackgroundBitmap(int Caller, int HOffset, int VOffset, int Element, int EntryPos, Graphics::TBitmap *GraphicPtr) const
3163 {
3164  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PickUpBackgroundBitmap," + AnsiString(HOffset) + "," +
3165  AnsiString(VOffset) + "," + AnsiString(Element) + "," + AnsiString(EntryPos) + "," + HeadCode);
3166  Graphics::TBitmap *EXGraphicPtr = RailGraphics->bmTransparentBgnd;
3167  // default values
3168  Graphics::TBitmap *EntryDirectionGraphicPtr = RailGraphics->bmTransparentBgnd;
3169 
3170  TAllRoutes::TRouteType RouteType;
3171 
3172  RouteType = AllRoutes->GetRouteTypeAndGraphics(11, Element, EntryPos, EXGraphicPtr, EntryDirectionGraphicPtr);
3173 
3174  TRect SourceRect, DestRect;
3175 
3176  DestRect.init(0, 0, 8, 8); // initialise left, top, right, bottom
3177  // note right and bottom rect co-ordinates are 1 greater than the pixel area
3178  SourceRect.init(HOffset, VOffset, HOffset + 8, VOffset + 8);
3179  Graphics::TBitmap *TempGraphic = new Graphics::TBitmap;
3180 
3181  TempGraphic->PixelFormat = pf8bit;
3182  TempGraphic->Width = 16;
3183  TempGraphic->Height = 16;
3184  TTrackElement TempElement = Track->TrackElementAt(286, Element);
3185 
3186  if(TempElement.TrackType == Points)
3187  {
3188  TempGraphic->Assign(TempElement.GraphicPtr);
3189  TempGraphic->Transparent = true;
3190  TempGraphic->TransparentColor = Utilities->clTransparent;
3191  if(RouteType == TAllRoutes::AutoSigsRoute)
3192  {
3193  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3194  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3195  }
3196  else
3197  {
3198  TempGraphic->Canvas->Draw(0, 0, Track->GetFilletGraphic(1, TempElement)); // add fillet
3199  }
3200  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3201  }
3202  else if(TempElement.TrackType == GapJump) // plot set gap
3203  {
3204  if(TempElement.SpeedTag == 88)
3205  {
3206  TempGraphic->Assign(RailGraphics->gl88set);
3207  }
3208  else if(TempElement.SpeedTag == 89)
3209  {
3210  TempGraphic->Assign(RailGraphics->gl89set);
3211  }
3212  else if(TempElement.SpeedTag == 90)
3213  {
3214  TempGraphic->Assign(RailGraphics->gl90set);
3215  }
3216  else if(TempElement.SpeedTag == 91)
3217  {
3218  TempGraphic->Assign(RailGraphics->gl91set);
3219  }
3220  else if(TempElement.SpeedTag == 92)
3221  {
3222  TempGraphic->Assign(RailGraphics->gl92set);
3223  }
3224  else if(TempElement.SpeedTag == 93)
3225  {
3226  TempGraphic->Assign(RailGraphics->bm93set);
3227  }
3228  else if(TempElement.SpeedTag == 94)
3229  {
3230  TempGraphic->Assign(RailGraphics->bm94set);
3231  }
3232  else if(TempElement.SpeedTag == 95)
3233  {
3234  TempGraphic->Assign(RailGraphics->gl95set);
3235  }
3236  TempGraphic->Transparent = true;
3237  TempGraphic->TransparentColor = Utilities->clTransparent;
3238  if(RouteType == TAllRoutes::AutoSigsRoute)
3239  {
3240  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3241  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3242  }
3243  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3244  }
3245  // new for version 0.6
3246  else if(TempElement.TrackType == SignalPost)
3247  {
3248  if(TempElement.SigAspect == TTrackElement::GroundSignal)
3249  {
3250  for(int x = 0; x < 40; x++)
3251  {
3252  if((Track->SigTableGroundSignal[x].SpeedTag == TempElement.SpeedTag) && (Track->SigTableGroundSignal[x].Attribute == 0))
3253  // need to stop aspect
3254  {
3255  TempGraphic->Assign(Track->SigTableGroundSignal[x].SigPtr);
3256  break;
3257  }
3258  }
3259  }
3260  else // normal signal
3261  {
3262  TempGraphic->Assign(TempElement.GraphicPtr);
3263  // GraphicPtr set to normal signal in a signal track element
3264  }
3265  TempGraphic->Transparent = true;
3266  TempGraphic->TransparentColor = Utilities->clTransparent;
3267  if(RouteType == TAllRoutes::AutoSigsRoute)
3268  {
3269  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3270  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3271  }
3272  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3273  }
3274  else
3275  {
3276  // first check if there's a NamedNonStationLocation element at that position & if so pick up that as the background
3277  // can't name points gaps or signals so 'else' OK
3278  bool FoundFlag;
3279  TTrack::TIMPair IMPair = Track->GetVectorPositionsFromInactiveTrackMap(4, TempElement.HLoc, TempElement.VLoc, FoundFlag);
3280  if(FoundFlag)
3281  {
3283  {
3284  GraphicPtr->Canvas->CopyRect(DestRect, Track->InactiveTrackElementAt(26, IMPair.first).GraphicPtr->Canvas, SourceRect);
3285  TempGraphic->Assign(RailGraphics->bmName);
3286  TempGraphic->Transparent = true;
3287  TempGraphic->TransparentColor = Utilities->clTransparent;
3288  if(RouteType == TAllRoutes::AutoSigsRoute)
3289  {
3290  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3291  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3292  }
3293  else
3294  {
3295  TempGraphic->Canvas->Draw(0, 0, TempElement.GraphicPtr);
3296  }
3297  // draw track on top
3298  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3299  }
3300  else if(Track->InactiveTrackElementAt(116, IMPair.first).TrackType == LevelCrossing)
3301  {
3302  TempGraphic->Assign(TempElement.GraphicPtr);
3303  TempGraphic->Transparent = true;
3304  TempGraphic->TransparentColor = Utilities->clTransparent;
3305  // note that can't be an AutoSigsRoute
3306  // now overlay the LC central portion
3307  int BDVectorPos = -1; //not used
3308  if(Track->AnyLinkedBarrierDownVectorManual(0, Track->InactiveTrackElementAt(130, IMPair.first).HLoc, Track->InactiveTrackElementAt(131, IMPair.first).VLoc, BDVectorPos))
3309  {
3310  TempGraphic->Canvas->Draw(0, 0, RailGraphics->LCPlainMan);
3311  }
3312  else
3313  {
3314  TempGraphic->Canvas->Draw(0, 0, RailGraphics->LCPlain);
3315  }
3316  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3317  }
3318  else
3319  {
3320  TempGraphic->Assign(TempElement.GraphicPtr);
3321  TempGraphic->Transparent = true;
3322  TempGraphic->TransparentColor = Utilities->clTransparent;
3323  if(RouteType == TAllRoutes::AutoSigsRoute)
3324  {
3325  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3326  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3327  }
3328  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3329  }
3330  }
3331  else
3332  {
3333  TempGraphic->Assign(TempElement.GraphicPtr);
3334  TempGraphic->Transparent = true;
3335  TempGraphic->TransparentColor = Utilities->clTransparent;
3336  if(RouteType == TAllRoutes::AutoSigsRoute)
3337  {
3338  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3339  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3340  }
3341  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3342  }
3343  }
3344  delete TempGraphic;
3345  Utilities->CallLogPop(675);
3346 }
3347 
3348 // ---------------------------------------------------------------------------
3349 
3350 // This was an attempt to pick up the actual 8x8 graphic from the display, so that text & user graphics would show as soon as the train passed, and overwrite it with the
3351 // reconstructed track, and it works ok but for the little arrows showing route directions at start and end, which extend beyond the track. It doesn't matter for autosig
3352 // routes because they are replotted (along with the direction arrows) but for others they shouldn't be. Leave in in case an easy way to remove these pointers comes to mind.
3353 /*
3354 
3355  void TTrain::PickUpBackgroundBitmap(int Caller, int HOffset, int VOffset, int Element, int EntryPos, Graphics::TBitmap *GraphicPtr) const
3356  {
3357  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PickUpBackgroundBitmap," + AnsiString(HOffset) +
3358  "," + AnsiString(VOffset) + "," + AnsiString(Element) + "," + AnsiString(EntryPos) + "," + HeadCode);
3359  TAllRoutes::TRouteType RouteType;
3360  Graphics::TBitmap *EXGraphicPtr = RailGraphics->bmTransparentBgnd;
3361  // default values
3362  Graphics::TBitmap *EntryDirectionGraphicPtr = RailGraphics->bmTransparentBgnd;
3363  TTrackElement TempElement = Track->TrackElementAt(, Element); //this is a copy of the element passed into the function
3364  TRect SourceRect, DestRect, ScreenSourceRect;
3365  RouteType = AllRoutes->GetRouteTypeAndGraphics(7, Element, EntryPos, EXGraphicPtr, EntryDirectionGraphicPtr);
3366 
3367  DestRect.init(0, 0, 8, 8); //initialise left, top, right, bottom
3368  // note right and bottom rect co-ordinates are 1 greater than the pixel area
3369  SourceRect.init(HOffset, VOffset, HOffset + 8, VOffset + 8);
3370 
3371  //add text & user graphics if any to *GraphicPtr prior to adding the track
3372  int Left = ((TempElement.HLoc - Display->DisplayOffsetH) * 16) + HOffset;
3373  int Top = ((TempElement.VLoc - Display->DisplayOffsetV) * 16) + VOffset;
3374  int Right = Left + 8;
3375  int Bottom = Top + 8;
3376  ScreenSourceRect.init(Left, Top, Right, Bottom);
3377  GraphicPtr->Canvas->CopyMode = cmSrcCopy;
3378  GraphicPtr->Canvas->CopyRect(DestRect, Display->GetImage()->Canvas, ScreenSourceRect);
3379 
3380  Graphics::TBitmap *TempGraphic = new Graphics::TBitmap; //this will hold the 16x16 reconstructed element, prior to transfer of the 8x8 bit to *GraphicPtr
3381  TempGraphic->PixelFormat = pf8bit;
3382  TempGraphic->Width = 16;
3383  TempGraphic->Height = 16;
3384 
3385  Graphics::TBitmap *SourceGraphic = new Graphics::TBitmap; //this will hold the 8x8 element segment from TempGraphic - needed because to keep transparency have to use Draw, not CopyRect
3386  SourceGraphic->PixelFormat = pf8bit;
3387  SourceGraphic->Width = 16;
3388  SourceGraphic->Height = 16;
3389  SourceGraphic->Transparent = true;
3390  SourceGraphic->TransparentColor = Utilities->clTransparent;
3391 
3392  if (TempElement.TrackType == Points)
3393  {
3394  TempGraphic->Assign(TempElement.GraphicPtr);
3395  TempGraphic->Transparent = true;
3396  TempGraphic->TransparentColor = Utilities->clTransparent;
3397  if (RouteType == TAllRoutes::AutoSigsRoute)
3398  {
3399  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3400  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3401  }
3402  else
3403  {
3404  TempGraphic->Canvas->Draw(0, 0, Track->GetFilletGraphic(1, TempElement)); // add fillet
3405  }
3406  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3407  }
3408  else if (TempElement.TrackType == GapJump) // plot set gap
3409  {
3410  if (TempElement.SpeedTag == 88)
3411  TempGraphic->Assign(RailGraphics->gl88set);
3412  else if (TempElement.SpeedTag == 89)
3413  TempGraphic->Assign(RailGraphics->gl89set);
3414  else if (TempElement.SpeedTag == 90)
3415  TempGraphic->Assign(RailGraphics->gl90set);
3416  else if (TempElement.SpeedTag == 91)
3417  TempGraphic->Assign(RailGraphics->gl91set);
3418  else if (TempElement.SpeedTag == 92)
3419  TempGraphic->Assign(RailGraphics->gl92set);
3420  else if (TempElement.SpeedTag == 93)
3421  TempGraphic->Assign(RailGraphics->bm93set);
3422  else if (TempElement.SpeedTag == 94)
3423  TempGraphic->Assign(RailGraphics->bm94set);
3424  else if (TempElement.SpeedTag == 95)
3425  TempGraphic->Assign(RailGraphics->gl95set);
3426  TempGraphic->Transparent = true;
3427  TempGraphic->TransparentColor = Utilities->clTransparent;
3428  if (RouteType == TAllRoutes::AutoSigsRoute) {
3429  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3430  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3431  }
3432  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3433  }
3434  // new for version 0.6
3435  else if (TempElement.TrackType == SignalPost)
3436  {
3437  if (TempElement.SigAspect == TTrackElement::GroundSignal)
3438  {
3439  for (int x = 0; x < 40; x++)
3440  {
3441  if ((Track->SigTableGroundSignal[x].SpeedTag == TempElement.SpeedTag) && (Track->SigTableGroundSignal[x].Attribute == 0))
3442  // need to stop aspect
3443  {
3444  TempGraphic->Assign(Track->SigTableGroundSignal[x].SigPtr);
3445  break;
3446  }
3447  }
3448  }
3449  else // normal signal
3450  {
3451  TempGraphic->Assign(TempElement.GraphicPtr);
3452  // GraphicPtr set to normal signal in a signal track element
3453  }
3454  TempGraphic->Transparent = true;
3455  TempGraphic->TransparentColor = Utilities->clTransparent;
3456  if (RouteType == TAllRoutes::AutoSigsRoute) {
3457  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3458  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3459  }
3460  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3461  }
3462  else {
3463  // first check if there's a NamedNonStationLocation element at that position & if so pick up that as the background
3464  // can't name points gaps or signals so 'else' OK
3465  bool FoundFlag;
3466  TTrack::TIMPair IMPair = Track->GetVectorPositionsFromInactiveTrackMap
3467  (4, TempElement.HLoc, TempElement.VLoc, FoundFlag);
3468  if (FoundFlag)
3469  {
3470  if (Track->InactiveTrackElementAt(, IMPair.first).TrackType == NamedNonStationLocation)
3471  {
3472  GraphicPtr->Canvas->CopyRect(DestRect,
3473  Track->InactiveTrackElementAt(, IMPair.first).GraphicPtr->Canvas, SourceRect);
3474  TempGraphic->Assign(RailGraphics->bmName);
3475  TempGraphic->Transparent = true;
3476  TempGraphic->TransparentColor = Utilities->clTransparent;
3477  if (RouteType == TAllRoutes::AutoSigsRoute)
3478  {
3479  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3480  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3481  }
3482  else
3483  TempGraphic->Canvas->Draw(0, 0, TempElement.GraphicPtr);
3484  // draw track on top
3485  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas,
3486  SourceRect);
3487  }
3488  else if (Track->InactiveTrackElementAt(, IMPair.first).TrackType == LevelCrossing) {
3489  TempGraphic->Assign(TempElement.GraphicPtr);
3490  TempGraphic->Transparent = true;
3491  TempGraphic->TransparentColor = Utilities->clTransparent;
3492  // note that can't be an AutoSigsRoute
3493  // now overlay the LC central portion
3494  TempGraphic->Canvas->Draw(0, 0, RailGraphics->LCPlain);
3495  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas,
3496  SourceRect);
3497  }
3498  else {
3499  TempGraphic->Assign(TempElement.GraphicPtr);
3500  TempGraphic->Transparent = true;
3501  TempGraphic->TransparentColor = Utilities->clTransparent;
3502  if (RouteType == TAllRoutes::AutoSigsRoute) {
3503  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3504  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3505  }
3506  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas,
3507  SourceRect);
3508  }
3509  }
3510  else {
3511  TempGraphic->Assign(TempElement.GraphicPtr);
3512  TempGraphic->Transparent = true;
3513  TempGraphic->TransparentColor = Utilities->clTransparent;
3514  if (RouteType == TAllRoutes::AutoSigsRoute) {
3515  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3516  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3517  }
3518  SourceGraphic->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3519  GraphicPtr->Canvas->Draw(0, 0, SourceGraphic);
3520  }
3521  }
3522  delete TempGraphic;
3523  delete SourceGraphic;
3524  Utilities->CallLogPop();
3525  }
3526 */
3527 // ---------------------------------------------------------------------------
3528 
3529 void TTrain::PlotTrainGraphic(int Caller, int ArrayNumber, TDisplay *Disp)
3530 {
3531  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotTrainGraphic," + AnsiString(ArrayNumber) + "," + HeadCode);
3532  if(PlotElement[ArrayNumber] == -1)
3533  {
3534  Utilities->CallLogPop(676);
3535  return; // not plotted yet
3536  }
3537  SetTrainElementID(0, PlotElement[ArrayNumber], PlotEntryPos[ArrayNumber]);
3538  // set before plot so gap flashing stops first
3539  Disp->PlotOutput(29, ((Track->TrackElementAt(295, PlotElement[ArrayNumber]).HLoc * 16) + HOffset[ArrayNumber]),
3540  ((Track->TrackElementAt(296, PlotElement[ArrayNumber]).VLoc * 16) + VOffset[ArrayNumber]), HeadCodePosition[ArrayNumber]);
3541  // Only need to set ID for leading element, stays set until train finally leaves the element
3542  Plotted = true;
3543  Utilities->CallLogPop(677);
3544 }
3545 
3546 // ---------------------------------------------------------------------------
3547 
3548 void TTrain::PlotBackgroundGraphic(int Caller, int ArrayNumber, TDisplay *Disp) const
3549 {
3550  Disp->PlotOutput(30, ((Track->TrackElementAt(297, PlotElement[ArrayNumber]).HLoc * 16) + HOffset[ArrayNumber]),
3551  ((Track->TrackElementAt(298, PlotElement[ArrayNumber]).VLoc * 16) + VOffset[ArrayNumber]), BackgroundPtr[ArrayNumber]);
3552 }
3553 
3554 // ---------------------------------------------------------------------------
3555 
3556 bool TTrain::BufferAtExit(int Caller, int Element, int ExitPos) const
3557 {
3558  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",BufferAtExit," + AnsiString(Element) + "," + AnsiString(ExitPos) + "," +
3559  HeadCode);
3560  if((Track->TrackElementAt(299, Element).TrackType == Buffers) && (Track->TrackElementAt(300, Element).Config[ExitPos] == End))
3561  {
3562  Utilities->CallLogPop(678);
3563  return(true);
3564  }
3565  else
3566  {
3567  Utilities->CallLogPop(679);
3568  return(false);
3569  }
3570 }
3571 
3572 // ---------------------------------------------------------------------------
3573 
3574 bool TTrain::ContinuationExit(int Caller, int Element, int ExitPos) const
3575 {
3576  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ContinuationExit," + AnsiString(Element) + "," + AnsiString(ExitPos) +
3577  "," + HeadCode);
3578  if((Track->TrackElementAt(301, Element).TrackType == Continuation) && (Track->TrackElementAt(302, Element).Config[ExitPos] == End))
3579  {
3580  Utilities->CallLogPop(680);
3581  return(true);
3582  }
3583  else
3584  {
3585  Utilities->CallLogPop(681);
3586  return(false);
3587  }
3588 }
3589 
3590 // ---------------------------------------------------------------------------
3591 
3592 bool TTrain::IsTrainIDOnBridgeTrackPos01(int Caller, unsigned int TrackVectorPosition)
3593 // test whether this train on a bridge on trackpos 0 & 1
3594 {
3595  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsTrainIDOnBridgeTrackPos01," + AnsiString(TrackVectorPosition) + "," +
3596  HeadCode);
3597  if(Track->TrackElementAt(303, TrackVectorPosition).TrackType != Bridge)
3598  {
3599  Utilities->CallLogPop(682);
3600  return(false);
3601  }
3602  // if(Track->TrackElementAt(304, TrackVectorPosition).TrainIDOnElement != TrainID) return false; No, if a bridge could be one of 2 TrainIDs
3604  {
3606  {
3607  throw Exception("Error, same train on two different bridge tracks");
3608  }
3609  else
3610  {
3611  Utilities->CallLogPop(684);
3612  return(true);
3613  }
3614  }
3615  else
3616  {
3617  Utilities->CallLogPop(685);
3618  return(false);
3619  }
3620 }
3621 
3622 // ---------------------------------------------------------------------------
3623 
3624 bool TTrain::IsTrainIDOnBridgeTrackPos23(int Caller, unsigned int TrackVectorPosition)
3625 // test whether this train on a bridge on trackpos 2 & 3
3626 {
3627  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsTrainIDOnBridgeTrackPos23," + AnsiString(TrackVectorPosition) + "," +
3628  HeadCode);
3629  if(Track->TrackElementAt(307, TrackVectorPosition).TrackType != Bridge)
3630  {
3631  Utilities->CallLogPop(686);
3632  return(false);
3633  }
3634  // if(Track->TrackElementAt(308, TrackVectorPosition).TrainIDOnElement != TrainID) return false; No, if a bridge could be one of 2 TrainIDs
3636  {
3637  // don't carry out check for train on tracks 0 & 1 else will enter an infinite loop if train on both
3638  Utilities->CallLogPop(687);
3639  return(true);
3640  }
3641  else
3642  {
3643  Utilities->CallLogPop(688);
3644  return(false);
3645  }
3646 }
3647 
3648 // ---------------------------------------------------------------------------
3649 
3650 void TTrain::SetTrainElementID(int Caller, unsigned int TrackVectorPosition, int EntryPos)
3651 {
3652  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SetTrainElementID," + AnsiString(TrackVectorPosition) + "," +
3653  AnsiString(EntryPos) + "," + HeadCode);
3654  Track->TrackElementAt(310, TrackVectorPosition).TrainIDOnElement = TrainID;
3655 
3656  // unplot GapFlash graphics if land on flashing gap (this done before train plotted - see PlotTrainGraphic)
3657  if(Track->GapFlashFlag)
3658  {
3660  {
3663  Track->GapFlashFlag = false;
3664  }
3665  }
3666  if(Track->TrackElementAt(311, TrackVectorPosition).TrackType == Bridge)
3667  {
3668  if(EntryPos == -1)
3669  {
3670  throw Exception("Error, TrackVectorPosition set but not EntryPos in SetTrainElementID");
3671  }
3672  if(EntryPos < 2)
3673  {
3675  }
3676  else
3677  {
3679  }
3680  }
3681  Utilities->CallLogPop(690);
3682 }
3683 
3684 // ---------------------------------------------------------------------------
3685 
3686 void TTrain::ResetTrainElementID(int Caller, unsigned int TrackVectorPosition, int EntryPos)
3687 {
3688  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ResetTrainElementID," + AnsiString(TrackVectorPosition) + "," +
3689  AnsiString(EntryPos) + "," + HeadCode);
3690  if(Track->TrackElementAt(314, TrackVectorPosition).TrackType != Bridge)
3691  {
3692  Track->TrackElementAt(315, TrackVectorPosition).TrainIDOnElement = -1;
3693  }
3694  else
3695  {
3696  if(EntryPos == -1)
3697  {
3698  throw Exception("Error, TrackVectorPosition set but not EntryPos in ResetTrainElementID");
3699  }
3700  if(EntryPos < 2)
3701  {
3702  Track->TrackElementAt(316, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit01 = -1;
3703  }
3704  else
3705  {
3706  Track->TrackElementAt(317, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit23 = -1;
3707  }
3708  if((EntryPos < 2) && (Track->TrackElementAt(318, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit23 > -1))
3709  // i.e. other train on track 2&3
3710  {
3711  Track->TrackElementAt(319, TrackVectorPosition).TrainIDOnElement = Track->TrackElementAt(320, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit23;
3712  }
3713  else if((EntryPos > 1) && (Track->TrackElementAt(321, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit01 > -1))
3714  // i.e. other train on track 1&2
3715  {
3716  Track->TrackElementAt(322, TrackVectorPosition).TrainIDOnElement = Track->TrackElementAt(323, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit01;
3717  }
3718  else
3719  {
3720  Track->TrackElementAt(324, TrackVectorPosition).TrainIDOnElement = -1;
3721  }
3722  }
3723  Utilities->CallLogPop(691);
3724 }
3725 
3726 // ---------------------------------------------------------------------------
3727 
3728 void TTrain::PlotAlternativeTrackRouteGraphic(int Caller, unsigned int ElementVecNum, int ElementEntryPos, int HOffset, int VOffset, TStraddle StraddleValue)
3729 {
3730  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotAlternativeTrackRouteGraphic," + AnsiString(ElementVecNum) + "," +
3731  AnsiString(ElementEntryPos) + "," + AnsiString(HOffset) + "," + AnsiString(VOffset) + "," + AnsiString(StraddleValue) + "," + HeadCode);
3732  int LockedVectorNumber;
3733 
3734  if(Track->TrackElementAt(325, ElementVecNum).TrackType != Bridge)
3735  // && (Track->TrackElementAt(326, ElementVecNum).TrackType != Crossover))
3736  {
3737  // only applies for a bridge as there can't be (or shouldn't be) 2 routes on an element that isn't a bridge
3738  Utilities->CallLogPop(692);
3739  return;
3740  }
3741  if(AllRoutes->TrackIsInARoute(0, ElementVecNum, (3 - ElementEntryPos)))
3742  // i.e other track is in a marked route
3743  // LinkPos doesn't have to be the entry position for the above check
3744  {
3745  TRect SourceRect, DestRect;
3746  DestRect.init(0, 0, 8, 8); // left, top, right, bottom
3747  // note right and bottom rect co-ordinates are 1 greater than the pixel area
3748  SourceRect.init(HOffset, VOffset, HOffset + 8, VOffset + 8);
3749  // identify the route element for the other track
3750  TAllRoutes::TRouteElementPair RoutePair1, RoutePair2;
3751  RoutePair1 = AllRoutes->GetRouteElementDataFromRoute2MultiMap(13, Track->TrackElementAt(327, ElementVecNum).HLoc,
3752  Track->TrackElementAt(328, ElementVecNum).VLoc, RoutePair2);
3753  int FirstELink, SecondELink = -1;
3754  FirstELink = AllRoutes->GetFixedRouteAt(149, RoutePair1.first).GetFixedPrefDirElementAt(159, RoutePair1.second).GetELink();
3755  // must be at least one
3756  if(RoutePair2.first > -1)
3757  {
3758  SecondELink = AllRoutes->GetFixedRouteAt(150, RoutePair2.first).GetFixedPrefDirElementAt(160, RoutePair2.second).GetELink();
3759  }
3760  TPrefDirElement RouteElement;
3761  // Graphics::TBitmap *RouteGraphic;
3762  if(FirstELink == Track->TrackElementAt(329, ElementVecNum).Link[ElementEntryPos])
3763  // i.e. other track is in RoutePair2
3764  {
3765  if(SecondELink == -1)
3766  {
3767  throw Exception("Error - Second ELink should be set but isn't in PlotAlternativeTrackRouteGraphic [1]");
3768  }
3769  if(SecondELink == Track->TrackElementAt(330, ElementVecNum).Link[ElementEntryPos])
3770  // error if both have same Link number
3771  {
3772  throw Exception("Error - First & Second ELinks have same value in PlotAlternativeTrackRouteGraphic");
3773  }
3774  // RouteGraphic = AllRoutes->GetFixedRouteAt(151, RoutePair2.first).GetFixedPrefDirElementAt(161, RoutePair2.second).GetEXGraphicPtr();
3775  RouteElement = AllRoutes->GetFixedRouteAt(152, RoutePair2.first).GetFixedPrefDirElementAt(162, RoutePair2.second);
3776  }
3777  else // other track is in RoutePair1
3778  {
3779  // RouteGraphic = AllRoutes->GetFixedRouteAt(153, RoutePair1.first).GetFixedPrefDirElementAt(163, RoutePair1.second).GetEXGraphicPtr();
3780  RouteElement = AllRoutes->GetFixedRouteAt(154, RoutePair1.first).GetFixedPrefDirElementAt(164, RoutePair1.second);
3781  }
3782  Graphics::TBitmap *DestGraphic = new Graphics::TBitmap;
3783  DestGraphic->PixelFormat = pf8bit;
3784  DestGraphic->Width = 8;
3785  DestGraphic->Height = 8;
3786  DestGraphic->Transparent = true;
3787  // has to be transparent or will overwrite the track that the train has just left
3788  DestGraphic->TransparentColor = Utilities->clTransparent;
3789  DestGraphic->Canvas->CopyRect(DestRect, RouteElement.GetRouteEXGraphicPtr()->Canvas, SourceRect);
3790  Display->PlotOutput(31, (Track->TrackElementAt(331, ElementVecNum).HLoc * 16) + HOffset,
3791  (Track->TrackElementAt(332, ElementVecNum).VLoc * 16) + VOffset, DestGraphic);
3792  // plot locked route marker for other route if appropriate
3793  TPrefDirElement PrefDirElement; // holder for next call, unused
3794  // plot locked route marker if appropriate, but only when train leaves element completely as this is a 16x16 graphic
3795  if(StraddleValue == LeadMidLag)
3796  {
3798  PrefDirElement, LockedVectorNumber))
3799  {
3800  Display->PlotOutput(32, (Track->TrackElementAt(333, ElementVecNum).HLoc * 16), (Track->TrackElementAt(334, ElementVecNum).VLoc * 16),
3801  RailGraphics->LockedRouteCancelPtr[RouteElement.GetELink()]);
3802  }
3803  }
3804  delete DestGraphic;
3805  }
3806  // but - there may be a train on the other track - if so need to replot it else the section of route overwrites it
3807  // also can only be a bridge or trains either have already or soon will crash
3808  if(Track->TrackElementAt(335, ElementVecNum).TrackType != Bridge)
3809  {
3810  Utilities->CallLogPop(695);
3811  return;
3812  }
3813  if(ElementEntryPos > 1) // other train is on track 01
3814  {
3816  {
3818  }
3819  }
3820  else // other train is on track 23
3821  {
3823  {
3825  }
3826  }
3827  Utilities->CallLogPop(696);
3828 }
3829 
3830 // ---------------------------------------------------------------------------
3831 
3832 void TTrain::CheckAndCancelRouteForWrongEndEntry(int Caller, int Element, int EntryPos)
3833 {
3834  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckAndCancelRouteForWrongEndEntry," + AnsiString(Element) + "," +
3835  AnsiString(EntryPos) + "," + HeadCode);
3836  int RouteNumber;
3837  bool WrongRoute = false;
3838  TPrefDirElement RouteElement;
3840  TAllRoutes::TRoute2MultiMapIterator Route2MultiMapIterator;
3841 
3842  if(AllRoutes->GetRouteTypeAndNumber(11, Element, EntryPos, RouteNumber) == TAllRoutes::NoRoute)
3843  // here if single track element & no route, or double track element with no route at EntryPos but still need to check if on points or a crossover on non-route track,
3844  // and force-erase route if so (bridge OK of course) note that GetRouteTypeAndNumber allows for points having an EntryPos of 0 or 2 & still returns correct values
3845  {
3846  if((Track->TrackElementAt(340, Element).TrackType == Crossover) || (Track->TrackElementAt(341, Element).TrackType == Points))
3847  {
3848  if(AllRoutes->GetRouteTypeAndNumber(12, Element, (3 - EntryPos), RouteNumber) != TAllRoutes::NoRoute)
3849  // (3-EntryPos) guarantees other route (0->3; 1->2; 2->1; 3->0)
3850  {
3851  if(AllRoutes->GetFixedRouteAt(179, RouteNumber).PrefDirSize() > 2)
3852  {
3853  // don't call for stub end routes
3855  }
3856  AllRoutes->GetModifiableRouteAt(13, RouteNumber).ForceCancelRoute(1);
3857  Utilities->CallLogPop(697);
3858  return;
3859  }
3860  }
3861  // also need to check for a route on a crossing diagonal
3862  TTrackElement TrackElement = Track->TrackElementAt(892, Element);
3863  int LinkNumber = TrackElement.Link[EntryPos];
3864  if((LinkNumber == 1) || (LinkNumber == 3) || (LinkNumber == 7) || (LinkNumber == 9))
3865  {
3866  if(AllRoutes->DiagonalFouledByRoute(0, TrackElement.HLoc, TrackElement.VLoc, LinkNumber))
3867  {
3868  // for LinkNumber = 1, potentially fouled diagonals are at H-1, V, Lk 3 & H, V-1, Lk 7
3869  bool LogActionErrorCalled = false;
3870  // to ensure only called once if have 2 routes on the 2 crossed diagonals
3871  if(LinkNumber == 1)
3872  {
3873  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(0, TrackElement.HLoc - 1, TrackElement.VLoc, 3, RouteNumber))
3874  {
3875  if(AllRoutes->GetFixedRouteAt(207, RouteNumber).PrefDirSize() > 2)
3876  {
3877  // don't call for stub end routes
3879  LogActionErrorCalled = true;
3880  }
3881  AllRoutes->GetModifiableRouteAt(20, RouteNumber).ForceCancelRoute(3);
3882  }
3883  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(1, TrackElement.HLoc, TrackElement.VLoc - 1, 7, RouteNumber))
3884  // not else in case have different routes on each diagonal, though shouldn't be possible
3885  {
3886  if(!LogActionErrorCalled && AllRoutes->GetFixedRouteAt(208, RouteNumber).PrefDirSize() > 2)
3887  {
3888  // don't call for stub end routes
3890  }
3891  AllRoutes->GetModifiableRouteAt(21, RouteNumber).ForceCancelRoute(4);
3892  }
3893  }
3894 
3895  // for LinkNumber = 3, potentially fouled diagonals are at H+1, V, Lk 1 & H, V-1 Lk 9
3896  else if(LinkNumber == 3)
3897  {
3898  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(2, TrackElement.HLoc + 1, TrackElement.VLoc, 1, RouteNumber))
3899  {
3900  if(AllRoutes->GetFixedRouteAt(209, RouteNumber).PrefDirSize() > 2)
3901  {
3902  // don't call for stub end routes
3904  LogActionErrorCalled = true;
3905  }
3906  AllRoutes->GetModifiableRouteAt(22, RouteNumber).ForceCancelRoute(5);
3907  }
3908  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(3, TrackElement.HLoc, TrackElement.VLoc - 1, 9, RouteNumber))
3909  // not else in case have different routes on each diagonal, though shouldn't be possible
3910  {
3911  if(!LogActionErrorCalled && AllRoutes->GetFixedRouteAt(210, RouteNumber).PrefDirSize() > 2)
3912  {
3913  // don't call for stub end routes
3915  }
3916  AllRoutes->GetModifiableRouteAt(23, RouteNumber).ForceCancelRoute(6);
3917  }
3918  }
3919 
3920  // for LinkNumber = 7, potentially fouled diagonals are at H-1, V, Lk 9 & H, V+1 Lk 1
3921  else if(LinkNumber == 7)
3922  {
3923  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(4, TrackElement.HLoc - 1, TrackElement.VLoc, 9, RouteNumber))
3924  {
3925  if(AllRoutes->GetFixedRouteAt(211, RouteNumber).PrefDirSize() > 2)
3926  {
3927  // don't call for stub end routes
3929  LogActionErrorCalled = true;
3930  }
3931  AllRoutes->GetModifiableRouteAt(24, RouteNumber).ForceCancelRoute(7);
3932  }
3933  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(5, TrackElement.HLoc, TrackElement.VLoc + 1, 1, RouteNumber))
3934  // not else in case have different routes on each diagonal, though shouldn't be possible
3935  {
3936  if(!LogActionErrorCalled && AllRoutes->GetFixedRouteAt(212, RouteNumber).PrefDirSize() > 2)
3937  {
3938  // don't call for stub end routes
3940  }
3941  AllRoutes->GetModifiableRouteAt(25, RouteNumber).ForceCancelRoute(8);
3942  }
3943  }
3944 
3945  // for LinkNumber = 9, potentially fouled diagonals are at H+1, V, Lk 7 & H, V+1 Lk 3
3946  else if(LinkNumber == 9)
3947  {
3948  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(6, TrackElement.HLoc + 1, TrackElement.VLoc, 7, RouteNumber))
3949  {
3950  if(AllRoutes->GetFixedRouteAt(213, RouteNumber).PrefDirSize() > 2)
3951  {
3952  // don't call for stub end routes
3954  LogActionErrorCalled = true;
3955  }
3956  AllRoutes->GetModifiableRouteAt(26, RouteNumber).ForceCancelRoute(9);
3957  }
3958  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(7, TrackElement.HLoc, TrackElement.VLoc + 1, 3, RouteNumber))
3959  // not else in case have different routes on each diagonal, though shouldn't be possible
3960  {
3961  if(!LogActionErrorCalled && AllRoutes->GetFixedRouteAt(214, RouteNumber).PrefDirSize() > 2)
3962  {
3963  // don't call for stub end routes
3965  }
3966  AllRoutes->GetModifiableRouteAt(27, RouteNumber).ForceCancelRoute(10);
3967  }
3968  }
3969  }
3970  }
3971  Utilities->CallLogPop(698);
3972  return; // no route on other track or no other track
3973  }
3974  // here if there is a route at Element & EntryPos - so there can't be a route on the other track if a 4 track element, unless it's a bridge & that's ok
3975  for(unsigned int x = 0; x < AllRoutes->GetFixedRouteAt(155, RouteNumber).PrefDirSize(); x++)
3976  {
3977  RouteElement = AllRoutes->GetFixedRouteAt(156, RouteNumber).GetFixedPrefDirElementAt(165, x);
3978  bool PointsAtElement = (Track->TrackElementAt(987, Element).TrackType == Points); // new at v2.4.2 for points check - Xeon repoted it 30/05/20. He found that for routes that
3979  if(RouteElement.GetTrackVectorPosition() == (unsigned int)Element) // cross bridges at both levels can have entrypos 0 & other exitpos 2 so if don't have this check can cancel a route wrongly
3980  {
3981  if(RouteElement.GetELinkPos() == EntryPos)
3982  {
3983  Utilities->CallLogPop(699);
3984  return; // right direction
3985  }
3986  else if((RouteElement.GetELinkPos() == 2) && (EntryPos == 0) && PointsAtElement)
3987  {
3988  Utilities->CallLogPop(700);
3989  return; // right direction (points)
3990  }
3991  else if((RouteElement.GetELinkPos() == 0) && (EntryPos == 2) && PointsAtElement)
3992  {
3993  Utilities->CallLogPop(701);
3994  return; // right direction (points)
3995  }
3996  else if(RouteElement.GetXLinkPos() == EntryPos)
3997  {
3998  WrongRoute = true;
3999  break; // wrong direction
4000  }
4001  else if((RouteElement.GetXLinkPos() == 2) && (EntryPos == 0) && PointsAtElement) // ok for bridges
4002  {
4003  WrongRoute = true;
4004  break; // wrong direction
4005  }
4006  else if((RouteElement.GetXLinkPos() == 0) && (EntryPos == 2) && PointsAtElement) // ok for bridges
4007  {
4008  WrongRoute = true;
4009  break; // wrong direction
4010  }
4011  }
4012  }
4013  if(!WrongRoute)
4014  {
4015  throw Exception("Error, Element in route but no route found in CheckAndCancelRouteForWrongEndEntry");
4016  }
4017  if(AllRoutes->GetFixedRouteAt(180, RouteNumber).PrefDirSize() > 2)
4018  {
4019  // don't call for stub end routes
4021  }
4022  AllRoutes->GetModifiableRouteAt(14, RouteNumber).ForceCancelRoute(2);
4023  Utilities->CallLogPop(703);
4024 }
4025 
4026 // ---------------------------------------------------------------------------
4027 
4028 void TTrain::PlotTrainWithNewBackgroundColour(int Caller, TColor NewBackgroundColour, TDisplay *Disp)
4029 {
4030  if(BackgroundColour == NewBackgroundColour)
4031  {
4032  return; // don't replot if already correct
4033 
4034  }
4035  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotTrainWithNewBackgroundColour," + AnsiString(NewBackgroundColour));
4036  bool ColourError = false, ColourError2 = false;
4037 
4038  RailGraphics->ChangeBackgroundColour(1, FrontCodePtr, FrontCodePtr, NewBackgroundColour, BackgroundColour, ColourError);
4039  if(ColourError)
4040  {
4041  ColourError2 = true;
4042  }
4043  for(int x = 0; x < 4; x++)
4044  {
4045  RailGraphics->ChangeBackgroundColour(2, HeadCodeGrPtr[x], HeadCodeGrPtr[x], NewBackgroundColour, BackgroundColour, ColourError);
4046  if(ColourError)
4047  {
4048  ColourError2 = true;
4049  }
4050  }
4051  if(ColourError2)
4052  {
4053  TrainController->StopTTClockMessage(63, "ERROR: Colour depth insufficient to display train colours properly. Please ensure that the 'safe' (web) palette of "
4054  "256 colours can be displayed");
4055  }
4056  // NB need a separate 'for' loop since the plot order can be different from the graphic order depending on the direction
4057  // of motion
4058  for(int x = 0; x < 4; x++)
4059  {
4060  PlotTrainGraphic(6, x, Disp);
4061  }
4062  BackgroundColour = NewBackgroundColour;
4063  Display->Update();
4064  // need to keep this since Update() not called for PlotSmallOutput as too slow
4065  Utilities->CallLogPop(704);
4066 }
4067 
4068 // ---------------------------------------------------------------------------
4069 
4070 void TTrain::SetTrainMovementValues(int Caller, int TrackVectorPosition, int EntryPos)
4071 /*
4072 Note: Within the loop BrakeRate can only increase and MaxExitSpeed can only reduce
4073 
4074 Summary: Called during PlotStartPosition to set initial values, when stopped and need to restart, and during UpdateTrain when Straddle is LeadMidLag,
4075 i.e. just as the front of a train is about to move fully onto an element, where TrackVectorPosition is the element immediately in front
4076 of the element the front of the train is moving fully on to. The function calculates the times and speeds at the next half-element and
4077 full-element moves.
4078 
4079 Detail: TrackVectorPosition & EntryPos correspond to the TrackVector element immediately in front of where the train is at
4080 the end of the current Update(). EntrySpeed is needed but this is a class data member so isn't passed in. Set the
4081 train BrakeRate to zero (for now, likely to be altered later), & check if zero entry speed with another train directly in front & if so
4082 remain stopped. Pick up the half length value and speed limit for the EntryPos track, and set FrontElementLength to the length of the
4083 EntryPos track, then set LimitingSpeed to the minimum of the element speed limit or the train's maximum speed. Check if running past a
4084 red signal and set SPADFlag if so (use 1 for EntrySpeed rather than 0 as this value is a double so could be slightly in excess of 0).
4085 In this case set the brake rate to maximum to stop as soon as possible.
4086 
4087 For no SPAD calculate the distance that will be travelled at the maximum speed at which the train can exit the next element at half
4088 MaxBrakeRate, this is DistanceAtHalfBraking (also calculate DistanceAtThreeQuarterBraking - used for stopping under signaller control).
4089 DistanceAtHalfBraking is used as the limiting forward look from the next element (i.e. following EntryPos)
4090 for computing the actual braking rate. If no more restrictive speed limits or reasons to stop are found within the forward look then the
4091 train can accelerate or stay at its (local) maximum speed for the next element. The maximum speed on exit from the next element is used
4092 for calculating the forward look because it represents the worst case - i.e. assumes that the train accelerates for the next element.
4093 
4094 A loop is now entered where the CumulativeLength is updated and each successive element (if there are any - current element checked
4095 first to see whether buffers or continuation) in turn is examined: first the length of the
4096 current element is added to the cumulative length; then the half length and speedlimit are set for the next element - points are
4097 followed according to their current setting (Attribute), but derailments are ignored as these are dealt with outside this function; checks
4098 are then made to see whether the next element is a red signal (train should stop before it); next element is a buffer (train should stop
4099 at the end of it so the cumulative length has the next element length added); current element is a buffer (train should stop
4100 at the end of the current element so no need to alter the cumulative length); or have reached a named location stop position. For any of
4101 these reasons, or if stopping under signaller control, there is no more looping, instead the braking rate is calculated to bring the train
4102 to a stop over CumulativeLength. For all normal purposes the braking rate will be less than half (light braking), or less than three
4103 quarters if stopping under signaller control (heavy braking). However if signals are reset in front of a train then the train may need
4104 emergency braking (> 90% max brake rate) and a SPAD may result. Similarly if points are chaged in front of a train that divert it into a
4105 siding then again emeregency braking may be necessary and a crash may result.
4106 
4107 If the train is due to stop then the function calculates the half and full times and speeds and returns. However the calculation depends
4108 on the conditions at entry. If the EntrySpeed is lower than MaxHalfSpeed and the EntryPos element is the one
4109 that the train has to stop at the end of, as it might be for example if train had been stopped at a signal and the next element is a
4110 buffer, then the train accelerates for half the element and brakes for the other half.
4111 Now the BrakeRate is calculated (limited to the MaxBrakeRate), but if it is less than a value calculated at an earlier pass round
4112 the loop then it retains its earlier value (may be due to a close speed restriction that requires more braking than a more distant stop
4113 requirement). The MaxExitSpeedAtHalfBraking (maximum speed at which the train can leave the current element and still stop when required
4114 at half the max braking rate) value is also calculated using EntrySpeed and CumulativeLength, but limiting it to the line speed limit or
4115 train MaxRunningSpeed whichever is the lower. If EntrySpeed > MaxExitSpeedAtHalfBraking then braking is required, so the half and full
4116 speed and time values for the current element are calculated using BrakeRate, EntrySpeed and CurrentElementHalfLength. If need to stop
4117 at the end of the current elemecumulativent for other than a red signal (SPADs can occur) then ExitSpeedFull is set to 0. It should be calculated
4118 as 0 anyway for other than a red signal but this makes sure. If EntrySpeed <= MaxExitSpeedAtHalfBraking then can calculate the half and
4119 full speed and time values for acceleration over the current element, but limit ExitSpeedHalf & Full to MaxExitSpeedAtHalfBraking or to
4120 the current element speed limit if necessary. Check whether ExitSpeedHalf <= EntrySpeed (+0.01 since it's a double) and use constant speed
4121 time values for Half & Full if so, but prior to this increase EntrySpeed if necessary to avoid a divide by zero error.
4122 
4123 If the train is not due to stop within the DistanceAtHalfBraking from the next element following EntryPos then the next element (if there
4124 is one) is checked to see if its speed limit is less than the current value of LimitingSpeed (which is the minimum of any earlier element's
4125 speed limit that has been examined within the loop and the train's MaxRunning speed), and if so LimitingSpeed is set down to it. Now
4126 the MaxExitSpeedAtHalfBraking is calculated, limiting it to LimitingSpeed if less, in case need to accelerate in the current element, in
4127 which case the exit speeds need to be limited to MaxExitSpeedAtHalfBraking. If EntrySpeed > LimitingSpeed then calculate the braking rate
4128 to bring the speed down to LimitingSpeed in CumulativeLength, keeping the existing BrakeRate value if lower and keeping it within
4129 MaxBrakeRate.
4130 
4131 Then, providing the current element isn't a buffer or continuation, the 'Current' values are updated from the 'Next' values ready for
4132 the next loop iteration. The loop is broken out of if the current element is a buffer or continuation, the next element is a
4133 continuation, or (CumulativeLength - FrontElementLength) >= DistanceAtHalfBraking.
4134 
4135 Now the final Half and Full values can be set for braking (if BrakeRate > 0.01), or accelerating - limiting the half and full exit speed
4136 values to MaxExitSpeedAtHalfBraking if necessary, and using constant speed time values if the exit speeds aren't much different to
4137 EntrySpeed and EntrySpeed > 0.01 (to avoid a divide by zero error).
4138 
4139 Note that in no circumstances will a train stop when straddling 3 elements, it will always be fully on two elements. This is ensured
4140 by UpdateTrain() which never sets any stop conditions unless the train is fully on 2 elements when that function returns, i.e. entered
4141 when Straddle == LeadMidLag
4142 */
4143 {
4144  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SetTrainMovementValues," + AnsiString(TrackVectorPosition) + "," +
4145  AnsiString(EntryPos) + "," + HeadCode);
4146  int EntryHalfLength, CurrentElementHalfLength, NextElementHalfLength, CumulativeLength = 0, CurrentTrackVectorPosition = TrackVectorPosition;
4147  int DistanceAtHalfBraking, DistanceAtThreeQuarterBraking, ExitPos, NextTrackVectorPosition, NextEntryPos;
4148  bool RedSignalFlag = false, BuffersFlag = false, StationFlag = false, BuffersOrContinuationNowFlag = false, ContinuationNextFlag = false,
4149  TrainInFrontInSignallerModeFlag = false;
4150  double LimitingSpeed, FrontElementMaxSpeed, MaxExitSpeedAtHalfBrakingSquared, MaxExitSpeedAtHalfBraking, NextSpeedLimit, TempBrakeRate;
4151  double ExitSpeedHalfSquared, ExitSpeedFullSquared;
4152  bool SignallerStopRequired = false;
4153 
4155  // set high to begin with to avoid divide by zero errors on restart after stops, will be set lower later
4156 
4157  // Member variables: EntrySpeed, ExitSpeedHalf, ExitSpeedFull, MaxExitSpeed, BrakeRate, EntryTime, ExitTimeHalf, ExitTimeFull, FrontElementSpeedLimit, FrontElementLength;
4158 
4159  OneLengthAccelDecel = false;
4160  BrakeRate = 0;
4161  if(PowerAtRail < 1)
4162  {
4163  BrakeRate = CoastingBrakeRate; //brings train to a stop in 13km in 15min from starting speed of 100km/h (from research)
4164  }
4165 //find FrontElementLength & FrontElementSpeedLimit (these correspond to TrackVectorPosition input value);
4166  if(CurrentTrackVectorPosition > -1)
4167  {
4168  if(Track->TrackElementAt(855, CurrentTrackVectorPosition).TrackType == Points) // this test & section added at v0.6
4169  {
4170  if((EntryPos == 0) || (EntryPos == 2))
4171  {
4172  if(Track->TrackElementAt(856, CurrentTrackVectorPosition).Attribute == 0)
4173  {
4174  CurrentElementHalfLength = (Track->TrackElementAt(857, CurrentTrackVectorPosition).Length01) / 2;
4175  FrontElementSpeedLimit = Track->TrackElementAt(858, CurrentTrackVectorPosition).SpeedLimit01;
4176  }
4177  else
4178  {
4179  CurrentElementHalfLength = (Track->TrackElementAt(859, CurrentTrackVectorPosition).Length23) / 2;
4180  FrontElementSpeedLimit = Track->TrackElementAt(860, CurrentTrackVectorPosition).SpeedLimit23;
4181  }
4182  }
4183  else if(EntryPos == 1)
4184  {
4185  CurrentElementHalfLength = (Track->TrackElementAt(861, CurrentTrackVectorPosition).Length01) / 2;
4186  FrontElementSpeedLimit = Track->TrackElementAt(862, CurrentTrackVectorPosition).SpeedLimit01;
4187  }
4188  else // == 3
4189  {
4190  CurrentElementHalfLength = (Track->TrackElementAt(863, CurrentTrackVectorPosition).Length23) / 2;
4191  FrontElementSpeedLimit = Track->TrackElementAt(864, CurrentTrackVectorPosition).SpeedLimit23;
4192  }
4193  }
4194  else
4195  {
4196  if(EntryPos > 1)
4197  {
4198  CurrentElementHalfLength = (Track->TrackElementAt(348, CurrentTrackVectorPosition).Length23) / 2;
4199  FrontElementSpeedLimit = Track->TrackElementAt(349, CurrentTrackVectorPosition).SpeedLimit23;
4200  }
4201  else
4202  {
4203  CurrentElementHalfLength = (Track->TrackElementAt(350, CurrentTrackVectorPosition).Length01) / 2;
4204  FrontElementSpeedLimit = Track->TrackElementAt(351, CurrentTrackVectorPosition).SpeedLimit01;
4205  }
4206  }
4207  EntryHalfLength = CurrentElementHalfLength;
4208  FrontElementLength = 2 * CurrentElementHalfLength;
4209  }
4210  else
4211  {
4212  throw Exception("Error - CurrentTrackVectorPosition < 0 in SetTrainMovementValues");
4213  }
4214  if((CurrentElementHalfLength < 0) || (FrontElementSpeedLimit < 0))
4215  {
4216  throw Exception("Error - HalfLength or SpeedLimit < 0 in SetTrainMovementValues");
4217  }
4218  // check if zero entry speed with another train directly in front & if so remain stopped
4219  if((EntryPos > -1) && Track->OtherTrainOnTrack(2, CurrentTrackVectorPosition, EntryPos, TrainID) && (EntrySpeed < 1))
4220  {
4221  EntrySpeed = 0;
4222  ExitSpeedHalf = 0;
4223  ExitSpeedFull = 0;
4224  MaxExitSpeed = 0;
4225  BrakeRate = 0;
4226  ExitTimeHalf = EntryTime + TDateTime(1/24); //set this high in case used later though unlikely
4227  ExitTimeFull = EntryTime + TDateTime(1/23); //set about 2.5 mins later than half time
4228  StoppedForTrainInFront = true;
4229  TrainInFront = true;
4230  Utilities->CallLogPop(705);
4231  return;
4232  }
4233  // new at v2.4.0 - check for stopped and zero power
4234  if((EntrySpeed < 1) && PowerAtRail < 1)
4235  {
4236  EntrySpeed = 0;
4237  ExitSpeedHalf = 0;
4238  ExitSpeedFull = 0;
4239  MaxExitSpeed = 0;
4240  BrakeRate = 0;
4241  ExitTimeHalf = EntryTime + TDateTime(1/24); //set this high in case used later though unlikely
4242  ExitTimeFull = EntryTime + TDateTime(1/23); //set about 2.5 mins later than half time
4243  StoppedWithoutPower = true;
4244  Utilities->CallLogPop(2125);
4245  return;
4246  }
4247 //set LimitingSpeed & FrontElementMaxSpeed (internal values)
4248  if(BeingCalledOn)
4249  {
4250  LimitingSpeed = CallOnMaxSpeed;
4251  }
4252  else
4253  {
4254  LimitingSpeed = MaximumSpeedLimit;
4255  }
4256  if(LimitingSpeed > FrontElementSpeedLimit)
4257  {
4258  LimitingSpeed = FrontElementSpeedLimit;
4259  }
4260  if(LimitingSpeed > MaxRunningSpeed) //MaxRunningSpeed is set in AddTrain depending on timetable or signaller control mode
4261  {
4262  LimitingSpeed = MaxRunningSpeed;
4263  }
4264  FrontElementMaxSpeed = LimitingSpeed;
4265 
4266 /*
4267  for braking the deceleration rate is constant so the following formuli (Newton's Laws) are used:-
4268  (1) V^2/(3.6^2) = U^2/(3.6^2) - 2FS;
4269  (2) V/3.6 = U/3.6 - FT;
4270  (3) S = UT/3.6 - 0.5FT^2
4271  where(V = final speed in kph [km/h/3.6 = m/s], U = initial speed in km/h, F = deceleration rate in m/s/s, S = distance in m & T = time in secs)
4272 
4273  for accelerating the energy input rate (PowerAtRail) is constant so the following formuli are used:-
4274  (4) V^2/(3.6^2) - U^2/(3.6^2) = A^2T;
4275  (5) V = 3.6 * ((1.5*S*A^2) + U^3/ (3.6)^3)^0.333334;
4276  where A is a constant (2*PowerAtRail/Mass)^0.5; V = final speed in kph, U = initial speed in kph , S = distance in m & T = time in secs
4277  It's a bit unrealistic during the early acceleration phase as it will be too rapid, but shouldn't affect the running unduly
4278 
4279  calc max speed that can attain on exit from next element (as could accelerate over next element) and use that speed to calc
4280  DistanceAtHalfBraking, if use actual speed may miss a stop requirement just outside look-ahead & accelerate, and at next calc
4281  be unable to stop or have hard acceleration followed immediately by hard braking, this speed makes for smoother operation
4282 */
4283 
4284 // check if running past a red signal without permission
4285  if((Track->TrackElementAt(352, CurrentTrackVectorPosition).Config[Track->GetNonPointsOppositeLinkPos(EntryPos)] == Signal) &&
4286  (Track->TrackElementAt(353, CurrentTrackVectorPosition).Attribute == 0) && (EntrySpeed > 1) && !AllowedToPassRedSignal &&
4287  !Track->TrackElementAt(1553, CurrentTrackVectorPosition).CallingOnSet)
4288  { //CallingOnSet added at v2.14.0
4289  SPADFlag = true; // user has to intervene to reset & restart after spad
4290  }
4291  if(!SPADFlag)
4292  {
4293  ExitSpeedFull = 3.6 * Power(((3 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)), 0.333334);
4294  // to begin with calc the maximum exit speed (assumes accelerating) and then reduce it if necessary
4295  // for accelerating the energy input rate (PowerAtRail) is constant so the following formuli are used:-
4296  // (1) V^2/(3.6^2) - U^2/(3.6^2) = A^2T; (2) V = 3.6 * ((1.5*S*A^2) + U^3/(3.6^3))^0.333334;
4297  // where A is a constant (2*PowerAtRail/Mass)^0.5; V = final speed in kph, U = initial speed in kph, S = distance & T = time, note that km/h/3.6 = m/s
4298  // This is a bit unrealistic during the early acceleration phase as it will be too rapid, but shouldn't affect the running unduly
4299 
4300  double ExitSpeedAtMaxBraking;
4301  // below introduced at v2.4.0, was ExitSpeedFull = LimitingSpeed; but that allowed very high brake rates when
4302  // took signaller control of a fast failed train with signaller limiting speed 30km/h
4304  {
4305  ExitSpeedAtMaxBraking = 0;
4306  }
4307  else
4308  {
4309  ExitSpeedAtMaxBraking = sqrt((EntrySpeed * EntrySpeed) - 2 * MaxBrakeRate * FrontElementLength);
4310  }
4311  double SpeedToUse;
4312  // use the highest of LimitingSpeed or ExitSpeedAtMaxBraking - added at v2.4.2 because trains entering at a continuation with zero (or very low) speed
4313  // & 2 elements before signal caused ExitSpeedAtMaxBraking & hence DistanceAtHalfBraking and DistanceAtThreeQuarterBraking to be zero, so no restriction was recognised
4314  // for first element & train accelerated at maximum rate, then at 2nd element train couldn't brake in time and overran the signal - notified by Micke via Discord on 02/06/20
4315  if(ExitSpeedAtMaxBraking > LimitingSpeed)
4316  {
4317  SpeedToUse = ExitSpeedAtMaxBraking;
4318  }
4319  else
4320  {
4321  SpeedToUse = LimitingSpeed;
4322  }
4323  if(ExitSpeedFull > SpeedToUse)
4324  {
4325  ExitSpeedFull = SpeedToUse;
4326  }
4327  DistanceAtHalfBraking = ExitSpeedFull * ExitSpeedFull / 3.6 / 3.6 / MaxBrakeRate;
4328  DistanceAtThreeQuarterBraking = ExitSpeedFull * ExitSpeedFull / 3.6 / 3.6 / 1.5 / MaxBrakeRate; // used for signaller stops
4329 
4330  //now enter a do loop to examine each element in turn from the front of the train to calc the cumulative length and to see if a stop is required (flag set if so -
4331  //RedSignalFlag, BuffersFlag, StationFlag, TrainInFrontInSignallerModeFlag, SignallerStopRequired, StepForwardFlag) in which case there are no more loops
4332  // break out of the loop when ((CumulativeLength - FrontElementLength) < DistanceAtHalfBraking ) && ((!BuffersOrContinuationNowFlag && !ContinuationNextFlag) || SignallerStoppingFlag);
4333 
4334  do
4335  {
4336  RedSignalFlag = false;
4337  BuffersFlag = false;
4338  StationFlag = false;
4339  BuffersOrContinuationNowFlag = false;
4340  ContinuationNextFlag = false;
4341  // have to reset this after the above test
4342  // add current element length to CumulativeLength
4343  CumulativeLength += (2 * CurrentElementHalfLength);
4344  if((CumulativeLength >= DistanceAtThreeQuarterBraking) && (TrainMode == Signaller) && SignallerStoppingFlag)
4345  {
4346  SignallerStopRequired = true;
4347  // once set stays set until SignallerStoppingFlag reset, providing !BuffersOrContinuationNowFlag,
4348  // set SignallerStopBrakeRate to stop in CumulativeLength unless already higher (i.e. can only increase)
4349  double TempBR = EntrySpeed * EntrySpeed / 2 / 3.6 / 3.6 / CumulativeLength;
4350  if(SignallerStopBrakeRate < TempBR)
4351  {
4352  SignallerStopBrakeRate = TempBR;
4353  }
4354  }
4355  // first check for stops within the length of the current element, where don't want any more checks & don't want
4356  // to add in any extra to the CumulativeLength. Only applies for buffers & station stops as signals should have been caught
4357  // during the last loop when the NextTrackVectorPosition was the signal.
4358 
4359  // check if current element is a buffer
4360  if(Track->TrackElementAt(374, CurrentTrackVectorPosition).TrackType == Buffers)
4361  {
4362  // no need to add in the length of this element to CumulativeLength as already included
4363  BuffersFlag = true;
4364  }
4365  // check if current element is a station stop
4366  if(TrainMode == Timetable)
4367  {
4368  bool StopRequired = false;
4369  if(!TimetableFinished && (NameInTimetableBeforeCDT(12, Track->TrackElementAt(375, CurrentTrackVectorPosition).ActiveTrackElementName,
4370  StopRequired) > -1) && ((Track->TrackElementAt(376, CurrentTrackVectorPosition).StationEntryStopLinkPos1 == EntryPos) ||
4371  (Track->TrackElementAt(377, CurrentTrackVectorPosition).StationEntryStopLinkPos2 == EntryPos) ||
4372  (Track->TrackElementAt(1642, CurrentTrackVectorPosition).StationEntryStopLinkPos3 == EntryPos) ||
4373  (Track->TrackElementAt(1643, CurrentTrackVectorPosition).StationEntryStopLinkPos4 == EntryPos)))
4374  {
4375  // no need to add in the length of element to CumulativeLength
4376  if(StopRequired)
4377  {
4378  StationFlag = true;
4379  }
4380  }
4381  }
4382  else
4383  {
4384  StationFlag = false;
4385  }
4386  // set NextHalfLength & NextSpeedLimit, but only if current element not buffers or exit continuation - no next element for them
4387  if(((Track->TrackElementAt(354, CurrentTrackVectorPosition).TrackType == Buffers) || (Track->TrackElementAt(355,
4388  CurrentTrackVectorPosition).TrackType == Continuation)) && (EntryPos == 1))
4389  {
4390  BuffersOrContinuationNowFlag = true;
4391  }
4392  if(!BuffersOrContinuationNowFlag && !BuffersFlag && !StationFlag) // skip if buffers or station flags already set
4393  {
4394  if(Track->TrackElementAt(356, CurrentTrackVectorPosition).TrackType == Points)
4395  {
4396  if((EntryPos == 0) || (EntryPos == 2))
4397  {
4398  if(Track->TrackElementAt(357, CurrentTrackVectorPosition).Attribute == 0)
4399  {
4400  ExitPos = 1;
4401  }
4402  else
4403  {
4404  ExitPos = 3;
4405  }
4406  }
4407  else
4408  {
4409  ExitPos = 0;
4410  }
4411  }
4412  else
4413  {
4414  ExitPos = Track->GetNonPointsOppositeLinkPos(EntryPos);
4415  }
4416  NextTrackVectorPosition = Track->TrackElementAt(358, CurrentTrackVectorPosition).Conn[ExitPos];
4417  NextEntryPos = Track->TrackElementAt(359, CurrentTrackVectorPosition).ConnLinkPos[ExitPos];
4418  if(NextTrackVectorPosition > -1)
4419  {
4420  if(Track->TrackElementAt(845, NextTrackVectorPosition).TrackType == Points)
4421  // this test & section added at v0.6
4422  {
4423  if((NextEntryPos == 0) || (NextEntryPos == 2))
4424  {
4425  if(Track->TrackElementAt(846, NextTrackVectorPosition).Attribute == 0)
4426  {
4427  NextElementHalfLength = (Track->TrackElementAt(847, NextTrackVectorPosition).Length01) / 2;
4428  NextSpeedLimit = Track->TrackElementAt(848, NextTrackVectorPosition).SpeedLimit01;
4429  }
4430  else
4431  {
4432  NextElementHalfLength = (Track->TrackElementAt(849, NextTrackVectorPosition).Length23) / 2;
4433  NextSpeedLimit = Track->TrackElementAt(850, NextTrackVectorPosition).SpeedLimit23;
4434  }
4435  }
4436  else if(NextEntryPos == 1)
4437  {
4438  NextElementHalfLength = (Track->TrackElementAt(851, NextTrackVectorPosition).Length01) / 2;
4439  NextSpeedLimit = Track->TrackElementAt(852, NextTrackVectorPosition).SpeedLimit01;
4440  }
4441  else // == 3
4442  {
4443  NextElementHalfLength = (Track->TrackElementAt(853, NextTrackVectorPosition).Length23) / 2;
4444  NextSpeedLimit = Track->TrackElementAt(854, NextTrackVectorPosition).SpeedLimit23;
4445  }
4446  }
4447  else
4448  {
4449  if(NextEntryPos > 1)
4450  {
4451  NextElementHalfLength = (Track->TrackElementAt(360, NextTrackVectorPosition).Length23) / 2;
4452  NextSpeedLimit = Track->TrackElementAt(361, NextTrackVectorPosition).SpeedLimit23;
4453  }
4454  else
4455  {
4456  NextElementHalfLength = (Track->TrackElementAt(362, NextTrackVectorPosition).Length01) / 2;
4457  NextSpeedLimit = Track->TrackElementAt(363, NextTrackVectorPosition).SpeedLimit01;
4458  }
4459  }
4460  }
4461  else
4462  {
4463  throw Exception("Error - Trying to access NextTrackVectorPosition when none present in SetTrainMovementValues");
4464  }
4465  // now check for stops, first cover those where don't want to add in length of next element
4466  // check if next element is a red signal - Attr 0,
4467  // note that this doesn't apply to trains stopped at a red signal since the signal position is
4468  // CurrentTrackVectorPosition not NextTrackVectorPosition
4469  bool StopRequired;
4470  if(Track->TrackElementAt(364, NextTrackVectorPosition).Config[Track->GetNonPointsOppositeLinkPos(NextEntryPos)] == Signal)
4471  {
4472  if(Track->TrackElementAt(365, NextTrackVectorPosition).Attribute == 0)
4473  {
4474  // no need to add in the length of element to CumulativeLength
4475  RedSignalFlag = true;
4476  }
4477  // next element is a red signal
4478  }
4479  // check if current element is a station & next element contains a train - trains will always stop without crashing at a
4480  // station they are due to stop at even if there is a train in front blocking the normal stop position - providing there is
4481  // at least one platform element free
4483  CurrentTrackVectorPosition).ActiveTrackElementName, StopRequired) > -1) && Track->OtherTrainOnTrack(3, NextTrackVectorPosition, NextEntryPos, TrainID))
4484  {
4485  // no need to add in the length of element to CumulativeLength
4486  if(StopRequired)
4487  {
4488  StationFlag = true;
4489  }
4490  }
4491  // check if next element contains a train & in Signaller mode (always stops for train in front if in signaller mode)
4492  else if((TrainMode == Signaller) && Track->OtherTrainOnTrack(4, NextTrackVectorPosition, NextEntryPos, TrainID))
4493  // (Track->TrackElementAt(651, NextTrackVectorPosition).TrainIDOnElement > -1))
4494  {
4495  // no need to add in the length of element to CumulativeLength
4496  TrainInFrontInSignallerModeFlag = true;
4497  }
4498  // check if next element is a buffer, but if StepForwardFlag true then need to stop before reach the buffers
4499  else if((Track->TrackElementAt(366, NextTrackVectorPosition).TrackType == Buffers) && !StepForwardFlag)
4500  {
4501  // need to add in the length of that element to CumulativeLength
4502  CumulativeLength += Track->TrackElementAt(367, NextTrackVectorPosition).Length01;
4503  BuffersFlag = true;
4504  }
4505  // check if next element is a station stop
4507  NextTrackVectorPosition).ActiveTrackElementName, StopRequired) > -1) && ((Track->TrackElementAt(371,
4508  NextTrackVectorPosition).StationEntryStopLinkPos1 == EntryPos) || (Track->TrackElementAt(372,
4509  NextTrackVectorPosition).StationEntryStopLinkPos2 == EntryPos) || (Track->TrackElementAt(1644,
4510  NextTrackVectorPosition).StationEntryStopLinkPos3 == EntryPos) || (Track->TrackElementAt(1645,
4511  NextTrackVectorPosition).StationEntryStopLinkPos4 == EntryPos)))
4512  { // need to add in the length of that element to CumulativeLength if a stop required
4513  if(StopRequired)
4514  {
4515  StationFlag = true;
4516  CumulativeLength += Track->TrackElementAt(373, NextTrackVectorPosition).Length01;
4517  }
4518  }
4519  }
4520  //now can decide whether need to stop over CumulativeLength
4521  if(RedSignalFlag || BuffersFlag || StationFlag || TrainInFrontInSignallerModeFlag || SignallerStopRequired || StepForwardFlag) // no more loops
4522  {
4523  // have to come to a stop over CumulativeLength
4524  if(CumulativeLength == FrontElementLength)
4525  // will be if StepForwardFlag (if stopped to begin with on zero power then earlier check will intercept it and it won't reach here
4526  // only one length to go before stop so check whether need to accelerate for half length then brake for latter
4527  // half; calc speed at halfway point that corresponds to half braking rate for latter half of track element,
4528  // and if less than EntrySpeed then skip this section (don't need any acceleration)
4529  // if not calc speed at halfway point & if less than above set half speed to this value;
4530  // use constant acceleration in calculating half time point
4531  {
4532  MaxExitSpeed = 0;
4533  double MaxHalfSpeed;
4534  double MaxHalfSpeedAtHalfBraking = 3.6 * sqrt(MaxBrakeRate * FrontElementLength / 2);
4535  // have to halve the element length, & can't be zero or negative so no need to test
4536  // if(MaxHalfSpeedAtHalfBraking > LimitingSpeed) MaxHalfSpeed = LimitingSpeed; else MaxHalfSpeed = MaxHalfSpeedAtHalfBraking;
4537  if(MaxHalfSpeedAtHalfBraking > FrontElementMaxSpeed)
4538  {
4539  MaxHalfSpeed = FrontElementMaxSpeed;
4540  }
4541  else
4542  {
4543  MaxHalfSpeed = MaxHalfSpeedAtHalfBraking;
4544  }
4545  if(MaxHalfSpeed > (2 * EntrySpeed) && (PowerAtRail > 1)) //PowerAtRail condition added at v2.18.0
4546  // use 2x to prevent kangarooing at last element when had
4547  // been braking smoothly at less that 50% braking rate, 2x should prevent all but extreme cases
4548  {
4549  ExitSpeedHalf = 3.6 * Power(((1.5 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)),
4550  0.333334);
4551  bool HalfSpeedLimited = false;
4552  if(MaxHalfSpeed < ExitSpeedHalf)
4553  {
4554  ExitSpeedHalf = MaxHalfSpeed;
4555  HalfSpeedLimited = true;
4556  }
4557  if(PowerAtRail > 1)
4558  // added at v2.4.0 in case reach here with failed train, when can't use AValue in denominator as close zero
4559  {
4560  // [km/h/3.6 = m/s]
4561  ExitTimeHalf =
4562  EntryTime + TDateTime(((ExitSpeedHalf * ExitSpeedHalf) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue) / 86400.0);
4563  }
4564  else
4565  {
4566  ExitTimeHalf = EntryTime + TDateTime(EntryHalfLength * 3.6 / EntrySpeed / 86400.0);
4567  }
4568  // the above is the time taken to accelerate to ExitSpeedHalf, so if this is reached before the half
4569  // way point (i.e. HalfSpeedLimited is set) then the time will be too short; change later to equal the
4570  // braking time; not fully accurate but better to be equal than have a short acceleration period followed
4571  // by a long braking period
4572  ExitSpeedFull = 0;
4573  TempBrakeRate = ExitSpeedHalf * ExitSpeedHalf / 2 / 3.6 / 3.6 / EntryHalfLength;
4574  if(TempBrakeRate > MaxBrakeRate)
4575  {
4576  TempBrakeRate = MaxBrakeRate;
4577  }
4578  // shouldn't be but leave in anyway
4579  if(TempBrakeRate > BrakeRate)
4580  {
4581  BrakeRate = TempBrakeRate;
4582  }
4583  // BrakeRate may already have been set in an earlier loop so don't want to reduce it
4584  ExitTimeFull = ExitTimeHalf + TDateTime(ExitSpeedHalf / 3.6 / BrakeRate / 86400.0);
4585  if(HalfSpeedLimited)
4586  // this is the change referred to above
4587  {
4588  TDateTime BrakingTime = ExitTimeFull - ExitTimeHalf;
4589  ExitTimeHalf = EntryTime + BrakingTime;
4590  ExitTimeFull = ExitTimeHalf + BrakingTime;
4591  }
4592  OneLengthAccelDecel = true; //used in TrackTrainFloat in InterfaceUnit.cpp to show accelerating for first half move then decelerating
4593  Utilities->CallLogPop(1095);
4594  return;
4595  }
4596  }
4597  // set braking to achieve speed = 0 @ CumulativeLength up to MaxBrakeRate
4598  // calc MaxExitSpeed for element at EntryPosition & set to this or existing val if lower,
4599  // calc th, tf, sh, & sf
4600  TempBrakeRate = EntrySpeed * EntrySpeed / 2 / 3.6 / 3.6 / CumulativeLength;
4601  if(TempBrakeRate > MaxBrakeRate)
4602  {
4603  TempBrakeRate = MaxBrakeRate;
4604  }
4605  if(TempBrakeRate > BrakeRate)
4606  {
4607  BrakeRate = TempBrakeRate;
4608  }
4609  // BrakeRate may already have been set in an earlier loop so don't want to reduce it
4610  if(SignallerStopRequired)
4611  // set BrakeRate to max of its calculated value or SignallerStopBrakeRate
4612  {
4614  {
4616  // this prevents the brakerate from reducing for a signaller stop
4617  // regardless of other conditions that may change as progress round the loop
4618  }
4619  }
4621  // prevents BrakeRate dropping below SignallerStopBrakeRate once it's been set whether or not SignallerStopRequired set
4622  // SignallerStopRequired may not be set if a red signal found in a later calc, & brakerate may then drop
4623  {
4625  }
4626  int TempMaxExitSpeed;
4627  // calc current value & if less than MaxExitSpeed set that to this
4628  MaxExitSpeedAtHalfBrakingSquared = 3.6 * 3.6 * MaxBrakeRate * (CumulativeLength - FrontElementLength);
4629  if(MaxExitSpeedAtHalfBrakingSquared < 10)
4630  {
4631  MaxExitSpeedAtHalfBraking = 0;
4632  }
4633  else
4634  {
4635  MaxExitSpeedAtHalfBraking = sqrt(MaxExitSpeedAtHalfBrakingSquared);
4636  }
4637  // if(MaxExitSpeedAtHalfBraking > LimitingSpeed) MaxExitSpeed = LimitingSpeed; else MaxExitSpeed = MaxExitSpeedAtHalfBraking;
4638  // I think the above was dropped because it could cause MaxExitSpeed to increase (MaxExitSpeed is an external variable retained between loops)
4639  if(MaxExitSpeedAtHalfBraking > FrontElementMaxSpeed)
4640  {
4641  TempMaxExitSpeed = FrontElementMaxSpeed;
4642  }
4643  else
4644  {
4645  TempMaxExitSpeed = MaxExitSpeedAtHalfBraking;
4646  }
4647  if(TempMaxExitSpeed < MaxExitSpeed)
4648  {
4649  MaxExitSpeed = TempMaxExitSpeed;
4650  }
4651  // here have EntrySpeed & MaxExitSpeed (for the next element), BrakeRate (to bring speed to zero over
4652  // Cumulativelength, and Cumulativelength
4653 
4654  if((EntrySpeed > MaxExitSpeed) || SignallerStopRequired || (SignallerStopBrakeRate > 0.01)) // need to brake
4655  {
4656  ExitSpeedHalfSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 2 * BrakeRate * EntryHalfLength);
4657  if(ExitSpeedHalfSquared < 10)
4658  {
4659  ExitSpeedHalf = 0;
4660  }
4661  else
4662  {
4663  ExitSpeedHalf = sqrt(ExitSpeedHalfSquared);
4664  }
4665  ExitTimeHalf = EntryTime + TDateTime((EntrySpeed - ExitSpeedHalf) / 3.6 / BrakeRate / 86400.0);
4666  ExitSpeedFullSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 4 * BrakeRate * EntryHalfLength);
4667  if(ExitSpeedFullSquared < 10)
4668  {
4669  ExitSpeedFull = 0;
4670  }
4671  else
4672  {
4673  ExitSpeedFull = sqrt(ExitSpeedFullSquared);
4674  }
4675  if((StationFlag) && (CumulativeLength == FrontElementLength))
4676  {
4677  ExitSpeedFull = 0;
4678  // force a stop for station (not for buffers or red signal)
4679  }
4680  ExitTimeFull = EntryTime + TDateTime((EntrySpeed - ExitSpeedFull) / 3.6 / BrakeRate / 86400.0);
4681  }
4682  // new condition at v2.4.0
4683  else if(PowerAtRail < 1)
4684  // use EntrySpeed, CumulativeLength & BrakeRate to calculate the half and full exit times and speeds for next element
4685  // avoid using AValue in denominator or have excessively long times
4686  {
4687  ExitSpeedHalfSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * BrakeRate * FrontElementLength);
4688  ExitSpeedHalf = sqrt(ExitSpeedHalfSquared);
4689  ExitTimeHalf = EntryTime + TDateTime((EntrySpeed - ExitSpeedHalf) / (3.6 * BrakeRate * 86400.0));
4690 
4691  ExitSpeedFullSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 2 * BrakeRate * FrontElementLength);
4692  ExitSpeedFull = sqrt(ExitSpeedFullSquared);
4693  ExitTimeFull = EntryTime + TDateTime((EntrySpeed - ExitSpeedFull) / (3.6 * BrakeRate * 86400.0));
4694  }
4695  else // e.g. moving towards a signal or station after a speed limit, so can accelerate unless no power
4696  // without the power need above condition or have hours of delay times, above added at v2.4.0
4697  {
4698  // for accelerating the energy input rate (PowerAtRail) is constant so the following formuli are used:-
4699  // (1) V^2/(3.6^2) - U^2/(3.6^2) = A^2T; (2) V = 3.6 * ((1.5*S*A^2) + U^3/(3.6^3))^0.333334;
4700  // where A is a constant (2*PowerAtRail/Mass)^0.5; V = final speed in kph, U = initial speed in kph, S = distance & T = time, note that km/h/3.6 = m/s
4701  // This is a bit unrealistic during the early acceleration phase as it will be too rapid, but shouldn't affect the running unduly
4702  BrakeRate = 0;
4703  ExitSpeedHalf = 3.6 * Power(((1.5 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)),
4704  0.333334);
4705  ExitSpeedFull = 3.6 * Power(((3 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)), 0.333334);
4706  // above valid for ExitSpeedHalf & Full <= MaxExitSpeed
4708  // can accelerate continually over the half length
4709  {
4710  ExitTimeHalf = EntryTime + TDateTime(((ExitSpeedHalf * ExitSpeedHalf) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue)
4711  / 86400.0);
4713  // can accelerate continually over the full length
4714  // i.e both (ExitSpeedHalf <= MaxExitSpeed) & (ExitSpeedFull <= MaxExitSpeed)
4715  {
4716  ExitTimeFull =
4717  EntryTime + TDateTime(((ExitSpeedFull * ExitSpeedFull) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue) / 86400.0);
4718  }
4719  else // (ExitSpeedHalf <= MaxExitSpeed) but (ExitSpeedFull > MaxExitSpeed)
4720  // accelerate to MaxExitSpeed then reamin at this speed for rest of element
4721  {
4722  // added at v0.6 as a safeguard
4723  if(MaxExitSpeed < EntrySpeed)
4724  {
4726  }
4727  // to prevent DeltaExitTimeToMaxInSecs being negative
4728  if(MaxExitSpeed < 1)
4729  {
4730  MaxExitSpeed = 1;
4731  }
4732  // to prevent divide by zero error
4733  // below as was
4735  double DeltaExitTimeToMaxInSecs = ((MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue);
4736  double DistanceToMax = ((MaxExitSpeed * MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / 3.6 /
4737  (1.5 * AValue * AValue);
4738  double RemainingDistance = double(FrontElementLength) - DistanceToMax;
4739  double DeltaRemainingTimeInSecs = 3.6 * RemainingDistance / MaxExitSpeed;
4740  ExitTimeFull = EntryTime + TDateTime((DeltaExitTimeToMaxInSecs + DeltaRemainingTimeInSecs) / 86400.0);
4741  }
4742  }
4743  else // ExitSpeedHalf > MaxExitSpeed, so ExitSpeedFull must also be > MaxExitSpeed
4744  // accelerate over first half to MaxExitSpeed then remain at this speed for rest of the first and
4745  // second halves of the element
4746  {
4747  // added at v0.6 as a safeguard
4748  if(MaxExitSpeed < EntrySpeed)
4749  {
4751  }
4752  // to prevent DeltaExitTimeToMaxInSecs being negative
4753  if(MaxExitSpeed < 1)
4754  {
4755  MaxExitSpeed = 1; // to prevent divide by zero error
4756  }
4757  // below as was
4759  double DeltaExitTimeToMaxInSecs = ((MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue);
4760  double DistanceToMax = ((MaxExitSpeed * MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / 3.6 /
4761  (1.5 * AValue * AValue);
4762  double RemainingDistance = double(FrontElementLength / 2) - DistanceToMax;
4763  // remaining distance to half length
4764  double DeltaRemainingTimeInSecs = 3.6 * RemainingDistance / MaxExitSpeed;
4765  ExitTimeHalf = EntryTime + TDateTime((DeltaExitTimeToMaxInSecs + DeltaRemainingTimeInSecs) / 86400.0);
4767  ExitTimeFull = ExitTimeHalf + TDateTime(3.6 * EntryHalfLength / MaxExitSpeed / 86400.0);
4768  }
4769  }
4770  Utilities->CallLogPop(706);
4771  return;
4772  }
4773  else
4774  {
4775  if(!BuffersOrContinuationNowFlag)
4776  {
4777  if(NextSpeedLimit < LimitingSpeed)
4778  {
4779  LimitingSpeed = NextSpeedLimit;
4780  }
4781  }
4782  // calc max exit speed at half braking to ensure don't accelerate past it (if acceleration is required)
4783  int TempMaxExitSpeed;
4784  // calc current value & if less than MaxExitSpeed set that to this
4785  // Note that LimitingSpeed is the max value at the end of CumulativeLength, so MaxExitSpeedAtHalfBrakingSquared will be larger
4786  MaxExitSpeedAtHalfBrakingSquared = (LimitingSpeed * LimitingSpeed) + (3.6 * 3.6 * MaxBrakeRate * (CumulativeLength - FrontElementLength));
4787  if(MaxExitSpeedAtHalfBrakingSquared < 10)
4788  {
4789  MaxExitSpeedAtHalfBraking = 0;
4790  }
4791  else
4792  {
4793  MaxExitSpeedAtHalfBraking = sqrt(MaxExitSpeedAtHalfBrakingSquared);
4794  }
4795  if(MaxExitSpeedAtHalfBraking > FrontElementMaxSpeed)
4796  {
4797  TempMaxExitSpeed = FrontElementMaxSpeed;
4798  }
4799  else
4800  {
4801  TempMaxExitSpeed = MaxExitSpeedAtHalfBraking;
4802  }
4803  if(TempMaxExitSpeed < MaxExitSpeed)
4804  {
4805  MaxExitSpeed = TempMaxExitSpeed;
4806  }
4807  // MaxExitSpeed is an external variable & this can reduce its value
4808  if(EntrySpeed > LimitingSpeed)
4809  // note that LimitingSpeed is more restrictive than MaxExitSpeed
4810  // calc TempBrakeRate & set BrakeRate to this or keep existing val if higher
4811  {
4812  TempBrakeRate = ((EntrySpeed * EntrySpeed) - (LimitingSpeed * LimitingSpeed)) / 3.6 / 3.6 / 2 / CumulativeLength;
4813  if(TempBrakeRate > MaxBrakeRate)
4814  {
4815  TempBrakeRate = MaxBrakeRate;
4816  }
4817  // shouldn't be for speedlimits since all known in advance, but include anyway
4818  if(TempBrakeRate > BrakeRate)
4819  {
4820  BrakeRate = TempBrakeRate;
4821  }
4822  // BrakeRate may already have been set in an earlier loop so don't want to reduce it
4823  }
4824  }
4825  if(!BuffersOrContinuationNowFlag)
4826  {
4827  CurrentTrackVectorPosition = NextTrackVectorPosition;
4828  EntryPos = NextEntryPos;
4829  CurrentElementHalfLength = NextElementHalfLength;
4830  if(Track->TrackElementAt(378, NextTrackVectorPosition).TrackType == Continuation)
4831  {
4832  ContinuationNextFlag = true;
4833  }
4834  }
4835  }
4836  while(((CumulativeLength - FrontElementLength) < DistanceAtHalfBraking) && ((!BuffersOrContinuationNowFlag && !ContinuationNextFlag) ||
4838  // check from the end of the front element, if include the front element and could brake during it, then will skip further loops
4839  // & miss a stop requirement just beyond the front element. happened in Richard Standing's railway where a new service introduced
4840  // on a 100m length, with 20m length after & then a red signal - train accelerated over the 100m then caused a SPAD as too short a
4841  // stopping distance after it.
4842 
4843  //(!BuffersOrContinuationNowFlag && !ContinuationNextFlag) true when no continuation on either the next element and next but one element.
4844  //There won't be a buffer on the next element or would have caught earlier, just using this flag for convenience.
4845 
4846  // If SignallerStoppingFlag then don't exit loop because of an imminent continuation, because continuation
4847  // not immediately in front (if it is then LeadElement will be the continuation & SignallerStoppingFlag will be reset in UpdateTrain()),
4848  // need to at least give a chance to stop on signaller command, if keep moving until continuation is immediately in front then will
4849  // exit loop & that is OK as don't want to stop so close to a continuation, if that happens it means that the command to stop was given
4850  // too late
4851 
4852  // set final braking or acc'n speed & time values
4853  if(BrakeRate > 0.01)
4854  {
4855  ExitSpeedHalfSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 2 * BrakeRate * EntryHalfLength);
4856  if(ExitSpeedHalfSquared < 10)
4857  {
4858  ExitSpeedHalf = 0;
4859  }
4860  else
4861  {
4862  ExitSpeedHalf = sqrt(ExitSpeedHalfSquared);
4863  }
4864  ExitTimeHalf = EntryTime + TDateTime((EntrySpeed - ExitSpeedHalf) / 3.6 / BrakeRate / 86400.0);
4865  ExitSpeedFullSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 4 * BrakeRate * EntryHalfLength);
4866  if(ExitSpeedFullSquared < 10)
4867  {
4868  ExitSpeedFull = 0;
4869  }
4870  else
4871  {
4872  ExitSpeedFull = sqrt(ExitSpeedFullSquared);
4873  }
4874  ExitTimeFull = EntryTime + TDateTime((EntrySpeed - ExitSpeedFull) / 3.6 / BrakeRate / 86400.0);
4875  }
4876  else
4877  {
4878  // for accelerating the energy input rate (PowerAtRail) is constant so the following formuli are used:-
4879  // (1) V^2/(3.6^2) - U^2/(3.6^2) = A^2T; (2) V = 3.6 * ((1.5*S*A^2) + U^3/ (3.6)^3)^0.333334;
4880  // where A is a constant (2*PowerAtRail/Mass)^0.5; V = final speed in kph, U = initial speed in kph , S = distance in m & T = time
4881  // This is a bit unrealistic during the early acceleration phase as it will be too rapid, but shouldn't affect the running unduly
4882 
4883  BrakeRate = 0;
4884  ExitSpeedHalf = 3.6 * Power(((1.5 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)), 0.333334);
4885  ExitSpeedFull = 3.6 * Power(((3 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)), 0.333334);
4886  // above valid for ExitSpeedHalf & Full <= MaxExitSpeed
4888  {
4889  if(PowerAtRail > 1)
4890  // added at v2.4.0 in case reach here with failed train, when can't use AValue in denominator as close zero
4891  {
4892  // [km/h/3.6 = m/s]
4893  ExitTimeHalf = EntryTime + TDateTime(((ExitSpeedHalf * ExitSpeedHalf) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue)
4894  / 86400.0);
4895  }
4896  else
4897  {
4898  ExitTimeHalf = EntryTime + TDateTime(EntryHalfLength * 3.6 / EntrySpeed / 86400.0);
4899  }
4901  // (ExitSpeedHalf <= MaxExitSpeed) & (ExitSpeedFull <= MaxExitSpeed)
4902  {
4903  if(PowerAtRail > 1)
4904  // added at v2.4.0 in case reach here with failed train, when can't use AValue in denominator as close zero
4905  {
4906  // [km/h/3.6 = m/s]
4907  ExitTimeFull = EntryTime + TDateTime(((ExitSpeedFull * ExitSpeedFull) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue)
4908  / 86400.0);
4909  }
4910  else
4911  {
4912  ExitTimeFull = EntryTime + TDateTime(2 * EntryHalfLength * 3.6 / EntrySpeed / 86400.0);
4913  }
4914  }
4915  else // (ExitSpeedHalf <= MaxExitSpeed) & (ExitSpeedFull > MaxExitSpeed)
4916  {
4917  // added at v0.6 as a safeguard
4918  if(MaxExitSpeed < EntrySpeed)
4919  {
4921  }
4922  // to prevent DeltaExitTimeToMaxInSecs being negative
4923  if(MaxExitSpeed < 1)
4924  {
4925  MaxExitSpeed = 1; // to prevent divide by zero error
4926  }
4927  // below as was
4929  double DeltaExitTimeToMaxInSecs;
4930  double DistanceToMax;
4931  if(PowerAtRail > 1) // added at v2.4.0
4932  {
4933  DeltaExitTimeToMaxInSecs = ((MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue);
4934  DistanceToMax = ((MaxExitSpeed * MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / 3.6 /
4935  (1.5 * AValue * AValue);
4936  }
4937  else // shouldn't ever get here given that ExitSpeedFull > ExitSpeedHalf
4938  {
4939  DeltaExitTimeToMaxInSecs = 2 * EntryHalfLength * 3.6 / EntrySpeed;
4940  // these not really accurate but will be good enough
4941  DistanceToMax = EntryHalfLength;
4942  }
4943  double RemainingDistance = double(FrontElementLength) - DistanceToMax;
4944  double DeltaRemainingTimeInSecs = 3.6 * RemainingDistance / MaxExitSpeed;
4945  ExitTimeFull = EntryTime + TDateTime((DeltaExitTimeToMaxInSecs + DeltaRemainingTimeInSecs) / 86400.0);
4946  }
4947  }
4948  else // ExitSpeedHalf > MaxExitSpeed, ExitSpeedFull must be > MaxExitSpeed
4949  {
4950  // added at v0.6 as a safeguard
4951  if(MaxExitSpeed < EntrySpeed)
4952  {
4954  }
4955  // to prevent DeltaExitTimeToMaxInSecs being negative
4956  if(MaxExitSpeed < 1)
4957  {
4958  MaxExitSpeed = 1; // to prevent divide by zero error
4959  }
4960  // below as was
4962  double DeltaExitTimeToMaxInSecs;
4963  double DistanceToMax;
4964  if(PowerAtRail > 1) // added at v2.4.0
4965  {
4966  DeltaExitTimeToMaxInSecs = ((MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue);
4967  DistanceToMax = ((MaxExitSpeed * MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / 3.6 /
4968  (1.5 * AValue * AValue);
4969  }
4970  else
4971  {
4972  DeltaExitTimeToMaxInSecs = 2 * EntryHalfLength * 3.6 / EntrySpeed;
4973  // these not really accurate but will be good enough
4974  DistanceToMax = EntryHalfLength / 2;
4975  }
4976  double RemainingDistance = double(FrontElementLength / 2) - DistanceToMax; // remaining distance to half length
4977  double DeltaRemainingTimeInSecs = 3.6 * RemainingDistance / MaxExitSpeed;
4978  ExitTimeHalf = EntryTime + TDateTime((DeltaExitTimeToMaxInSecs + DeltaRemainingTimeInSecs) / 86400.0);
4980  ExitTimeFull = ExitTimeHalf + TDateTime(3.6 * EntryHalfLength / MaxExitSpeed / 86400.0);
4981  }
4982  }
4983  }
4984 
4985  else // SPADFlag set
4986  {
4988  ExitSpeedHalfSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 2 * BrakeRate * EntryHalfLength);
4989  if(ExitSpeedHalfSquared < 10)
4990  {
4991  ExitSpeedHalf = 0;
4992  }
4993  else
4994  {
4995  ExitSpeedHalf = sqrt(ExitSpeedHalfSquared);
4996  }
4997  ExitTimeHalf = EntryTime + TDateTime((EntrySpeed - ExitSpeedHalf) / 3.6 / BrakeRate / 86400.0);
4998  ExitSpeedFullSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 4 * BrakeRate * EntryHalfLength);
4999  if(ExitSpeedFullSquared < 10)
5000  {
5001  ExitSpeedFull = 0;
5002  }
5003  else
5004  {
5005  ExitSpeedFull = sqrt(ExitSpeedFullSquared);
5006  }
5007  ExitTimeFull = EntryTime + TDateTime((EntrySpeed - ExitSpeedFull) / 3.6 / BrakeRate / 86400.0);
5008 
5009  // check if the exit speed is < 80% of the stopping speed for the next element, and if so stop at end of this element
5010  // this is because would stop short of end of next element (in reality the time to reach the end of the next element
5011  // would be too short (could be so short as to make the train jump) as time is calculated purely on speed & brake rate);
5012  // 80% is used as the brake rate might be set to come to a halt at the end of the next element in which case the speed
5013  // will be the stopping speed.
5014  if(ExitSpeedFull > 0)
5015  {
5016  if(Track->TrackElementAt(746, CurrentTrackVectorPosition).TrackType == Points)
5017  {
5018  if((EntryPos == 0) || (EntryPos == 2))
5019  {
5020  if(Track->TrackElementAt(747, CurrentTrackVectorPosition).Attribute == 0)
5021  {
5022  ExitPos = 1;
5023  }
5024  else
5025  {
5026  ExitPos = 3;
5027  }
5028  }
5029  else
5030  {
5031  ExitPos = 0;
5032  }
5033  }
5034  else
5035  {
5036  ExitPos = Track->GetNonPointsOppositeLinkPos(EntryPos);
5037  }
5038  NextTrackVectorPosition = Track->TrackElementAt(748, CurrentTrackVectorPosition).Conn[ExitPos];
5039  NextEntryPos = Track->TrackElementAt(749, CurrentTrackVectorPosition).ConnLinkPos[ExitPos];
5040  if(NextTrackVectorPosition > -1) // not a continuation or buffer
5041  {
5042  int NextElementLength;
5043  if(NextEntryPos > 1)
5044  {
5045  NextElementLength = (Track->TrackElementAt(750, NextTrackVectorPosition).Length23);
5046  }
5047  else
5048  {
5049  NextElementLength = (Track->TrackElementAt(751, NextTrackVectorPosition).Length01);
5050  }
5051  double NextStoppingSpeed = sqrt(3.6 * 3.6 * 2 * BrakeRate * NextElementLength);
5052  if(ExitSpeedFull < (0.8 * NextStoppingSpeed))
5053  {
5054  ExitSpeedFull = 0;
5055  }
5056  }
5057  }
5058  }
5059  // allow all values to be set normally in case need to brake, then test for zero power & need to coast
5060  if(PowerAtRail < 1) // new at v2.4.0 note that km/h/3.6 = m/s
5061  {
5062  // bring to a stop in 20 elements at 100km/h & assume each 100m long for calculating exit times but if on a continuation maintain speed <--NO,
5063  //change to BrakeRate = CoastingBrakeRate = 0.03 and calc times etc as normal - because of Albie Vowles' error report of 231223 where noticed
5064  //that failed train treated track lengths of > 2km as 100m so very noticeable. Keep going for exiting at continuation.
5065 
5066  //Coasting deceleration rate from paper 'Real-time train motion parameter estimation using an Unscented Kalman Filter' at
5067  //'https://www.sciencedirect.com/science/article/pii/S0968090X22002212'. In particular Fig 6 in section 4.3 shows coasting from 400sec to
5068  //1000sec corresponds to speed drop from 140km/h to 80km/h, i.e. 60km/h in 600sec, equivalent to 0.02777m/s/s deceleration, so use 0.03m/s/s.
5069 
5070  if(LeadElement > -1)
5071  {
5073  // don't stop on a continuation either entering or leaving
5074  {
5077  MaxExitSpeed = LimitingSpeed;
5078  BrakeRate = 0;
5079  ExitTimeHalf = EntryTime + TDateTime((50 * 3.6 / (EntrySpeed) / 86400.0));
5080  // assume length is 50m for ease of calc
5081  ExitTimeFull = ExitTimeHalf + TDateTime((50 * 3.6 / (ExitSpeedHalf) / 86400.0));
5082  Utilities->CallLogPop(2126);
5083  return;
5084  }
5085  }
5086  else if(MidElement > -1)
5087  {
5089  {
5092  MaxExitSpeed = LimitingSpeed;
5093  BrakeRate = 0;
5094  ExitTimeHalf = EntryTime + TDateTime((50 * 3.6 / (EntrySpeed) / 86400.0));
5095  // assume length is 50m for ease of calc
5096  ExitTimeFull = ExitTimeHalf + TDateTime((50 * 3.6 / (ExitSpeedHalf) / 86400.0));
5097  Utilities->CallLogPop(2127);
5098  return;
5099  }
5100  }
5101  else if(LagElement > -1)
5102  {
5104  {
5107  MaxExitSpeed = LimitingSpeed;
5108  BrakeRate = 0;
5109  ExitTimeHalf = EntryTime + TDateTime((50 * 3.6 / (EntrySpeed) / 86400.0));
5110  // assume length is 50m for ease of calc
5111  ExitTimeFull = ExitTimeHalf + TDateTime((50 * 3.6 / (ExitSpeedHalf) / 86400.0));
5112  Utilities->CallLogPop(2128);
5113  return;
5114  }
5115  }
5116 /* dropped at v2.18.1 in favour of CoastingBrakeRate which = 0.03m/s/s = see above explanation
5117  if(EntrySpeed > 7.5) // keep going for at least another element
5118  {
5119  ExitSpeedHalf = EntrySpeed - 2.5;
5120  ExitSpeedFull = EntrySpeed - 5;
5121  MaxExitSpeed = LimitingSpeed;
5122  BrakeRate = 0;
5123  ExitTimeHalf = EntryTime + TDateTime((50 * 3.6 / (EntrySpeed - 1.25) / 86400.0));
5124  // assume length is 50m for ease of calc
5125  ExitTimeFull = ExitTimeHalf + TDateTime((50 * 3.6 / (ExitSpeedHalf - 1.25) / 86400.0));
5126  Utilities->CallLogPop(2129);
5127  return;
5128  }
5129  else // stop immediately, don't enter next element, floating label had displayed last exit speed full on 2nd half move so with zero when fully on element
5130  {
5131  // will appear to have slowed at steady rate
5132  ExitSpeedHalf = 0;
5133  ExitSpeedFull = 0;
5134  MaxExitSpeed = LimitingSpeed;
5135  BrakeRate = 0;
5136  ExitTimeHalf = EntryTime + TDateTime(1/24); //set this high in case used later though unlikely
5137  ExitTimeFull = EntryTime + TDateTime(1/23); //set about 2.5 mins later than half time
5138  StoppedWithoutPower = true;
5139  Utilities->CallLogPop(2130);
5140  return;
5141  }
5142 */
5143  }
5144  // TempBrakeRate=MinSingle; TempBrakeRate=MaxSingle; TempBrakeRate=MinDouble; TempBrakeRate=MaxDouble;//included to stop warnings from unused declarations in math.hpp
5145  // TempBrakeRate=MinExtended; TempBrakeRate=MaxExtended; TempBrakeRate=MinComp; TempBrakeRate=MaxComp;//included to stop warnings from unused declarations in math.hpp
5146  Utilities->CallLogPop(707);
5147 }
5148 // ---------------------------------------------------------------------------
5149 /*
5150  bool TTrain::IsTerminalStation(int TrackVectorPosition, int EntryPos)
5151  {
5152  int NextExitPos;
5153  TTrackElement NextElement = Track->TrackElementAt(379, TrackVectorPosition), TempElement;
5154  if(TimetableVector.empty()) return false;
5155  if(NextElement.ActiveTrackElementName != TimetableVector.begin()->LocationName) return false;
5156  while((NextElement.ActiveTrackElementName == TimetableVector.begin()->LocationName) && (NextElement.TrackType != Buffers))
5157  {
5158  //check for points & follow attribute, but don't worry about a derail as that dealt with elsewhere
5159  if((NextElement.TrackType != Points) || ((EntryPos != 0) && (EntryPos != 2)))
5160  {
5161  NextExitPos = Track->GetNonPointsOppositeLinkPos(EntryPos);
5162  }
5163  else if((NextElement.TrackType == Points) && ((EntryPos == 0) || (EntryPos == 2)))
5164  {
5165  if(NextElement.Attribute == 0) NextExitPos = 1; else NextExitPos = 3;
5166  }
5167  TempElement = Track->TrackElementAt(380, NextElement.Conn[NextExitPos]);//need temp as NextElement used in next step
5168  NextElement = TempElement;
5169  }
5170  if(NextElement.ActiveTrackElementName != TimetableVector.begin()->LocationName) return false;
5171  if(NextElement.TrackType == Buffers) return true;
5172  return false;//shouldn't reach here but include to prevent compiler return warning
5173  }
5174 */
5175 // ---------------------------------------------------------------------------
5176 
5177 int TTrain::NameInTimetableBeforeCDT(int Caller, AnsiString Name, bool &Stop)
5178 /*
5179  returns the number by which the train ActionVectorEntryPtr needs
5180  to be incremented to point to the location arrival entry or passtime entry before a change of direction. Used to display missed
5181  actions when a stop or pass location has been reached before other timetabled events have been carried out. If can't find it, or Name
5182  is "", -1 is returned. A change of direction is the limit of the search because a train may not stop at a location on the way out
5183  but stop on way back, and in these circumstances no actions have been missed. Stop indicates whether the train will stop at (true)
5184  or pass (false) the location.
5185 */{
5186  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",NameInTimetableBeforeCDT," + Name + "," + HeadCode);
5187  Stop = false;
5188  if(TimetableFinished || (Name == ""))
5189  {
5190  Utilities->CallLogPop(957);
5191  return(-1);
5192  }
5194 /*added the following check at v2.11.1 because of a fault found in Kevin Smith's railway (notified 02/01/22). CH01 started at
5195 Chester behind the stop position, but when it departed and this function was called it found Chester again with no cdt
5196 before it (it went round the Liverpool Loop), so it stopped again when it reached the stop position and reported all intermediate stops as having been
5197 missed. This check is for the train just having departed from the station in question, and if it has then any further stations
5198 with the same name are ignored - i.e. it stops a train from stopping at the same station twice in succession.
5199 */
5200  if(Ptr > &TrainDataEntryPtr->ActionVector.at(0))
5201  {
5202  Ptr--;
5203  if((Ptr->DepartureTime > TDateTime(-1)) && (Ptr->LocationName == Name))
5204  {
5205  if((Ptr->FormatType == TimeLoc) || (Ptr->FormatType == TimeTimeLoc))
5206  {
5207  Utilities->CallLogPop(2444);
5208  return(-1);
5209  }
5210  }
5211  }
5212  // start looking from current pointer position
5213  for(TActionVectorEntry *Ptr = ActionVectorEntryPtr; Ptr < &TrainDataEntryPtr->ActionVector.back(); Ptr++)
5214  {
5215  if((Ptr->Command == "cdt") || (Ptr->FormatType == Repeat))
5216  {
5217  break;
5218  }
5219  if((Ptr->ArrivalTime > TDateTime(-1)) && (Ptr->LocationName == Name))
5220  {
5221  if((Ptr->FormatType == TimeLoc) || (Ptr->FormatType == TimeTimeLoc))
5222  {
5223  Stop = true;
5224  Utilities->CallLogPop(960);
5225  return (Ptr - ActionVectorEntryPtr);
5226  }
5227  }
5228  if((Ptr->EventTime > TDateTime(-1)) && (Ptr->LocationName == Name) && (Ptr->Command == "pas"))
5229  {
5230  Utilities->CallLogPop(1517);
5231  return (Ptr - ActionVectorEntryPtr);
5232  }
5233  }
5234  Utilities->CallLogPop(959);
5235  return(-1); // not found a valid entry
5236 }
5237 
5238 // ---------------------------------------------------------------------------
5239 
5241 /* Checks forward from train LeadElement, following leading point attributes but ignoring trailing point attributes,
5242  until finds either a train or a signal/buffers/continuation/loop. If finds a train returns false, else returns true.
5243  Ignores the call-on signal.
5244 */{
5245  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ClearToNextSignal" + "," + HeadCode);
5246  int ReturnVal = 0;
5247  int ElementCount = 0;
5248 /* dropped at v2.12.0 as takes up a great deal of time unnecessarily - substitute 1000 elements instead and return true (very unlikely to need to search this far [10km at min length])
5249  for(unsigned int x = 0; x < Track->TrackVector.size(); x++)
5250  {
5251  Track->TrackElementAt(1031, x).TempTrackMarker01 = false;
5252  Track->TrackElementAt(1032, x).TempTrackMarker23 = false;
5253  }
5254 */
5255  int CurrentTrackVectorPosition = LeadElement, NextTrackVectorPosition;
5256  int EntryPos = LeadEntryPos, ExitPos, NextEntryPos;
5257 
5258  while(true)
5259  {
5260  if((Track->TrackElementAt(382, CurrentTrackVectorPosition).TrainIDOnElement > -1) && (Track->TrackElementAt(383,
5261  CurrentTrackVectorPosition).TrainIDOnElement != TrainID))
5262  {
5263  ReturnVal = 1;
5264  break;
5265  }
5266  if(((Track->TrackElementAt(384, CurrentTrackVectorPosition).TrackType == Buffers) || (Track->TrackElementAt(385,
5267  CurrentTrackVectorPosition).TrackType == Continuation)) && (EntryPos == 1))
5268  {
5269  ReturnVal = 2;
5270  break;
5271  }
5272  if((EntryPos < 2) && (Track->TrackElementAt(386, CurrentTrackVectorPosition).Config[1 - EntryPos] == Signal) && !Track->TrackElementAt(529,
5273  CurrentTrackVectorPosition).CallingOnSet && (LeadElement != CurrentTrackVectorPosition)) // CallingOnSet true when position lights lit,
5274  {//added LeadElement condition at v2.18.0 as train may be on the callon signal after CallOnSet false & don't want to return true for that
5275  ReturnVal = 3;
5276  break;
5277  }
5278 /* not needed at and after v2.12.0, see above
5279  if((Track->TrackElementAt(387, CurrentTrackVectorPosition).TrackType == Bridge) || (Track->TrackElementAt(388, CurrentTrackVectorPosition).TrackType == Crossover))
5280  {
5281  if((EntryPos < 2) && (Track->TrackElementAt(523, CurrentTrackVectorPosition).TempTrackMarker01))
5282  // must be a loop - reached same point as examined earlier
5283  {
5284  ReturnVal = 4;
5285  break;
5286  }
5287  else if((EntryPos > 1) && (Track->TrackElementAt(524, CurrentTrackVectorPosition).TempTrackMarker23))
5288  {
5289  ReturnVal = 4;
5290  break;
5291  }
5292  }
5293  else
5294  {
5295  if((Track->TrackElementAt(525, CurrentTrackVectorPosition).TempTrackMarker01) || (Track->TrackElementAt(526, CurrentTrackVectorPosition).TempTrackMarker23))
5296  {
5297  ReturnVal = 4;
5298  break;
5299  }
5300  }
5301  if(EntryPos < 2)
5302  {
5303  Track->TrackElementAt(389, CurrentTrackVectorPosition).TempTrackMarker01 = true;
5304  }
5305  else
5306  {
5307  Track->TrackElementAt(527, CurrentTrackVectorPosition).TempTrackMarker23 = true;
5308  }
5309 */
5310 
5311  if(Track->TrackElementAt(390, CurrentTrackVectorPosition).TrackType == Points)
5312  {
5313  if((EntryPos == 0) || (EntryPos == 2))
5314  {
5315  if(Track->TrackElementAt(391, CurrentTrackVectorPosition).Attribute == 0)
5316  {
5317  ExitPos = 1;
5318  }
5319  else
5320  {
5321  ExitPos = 3;
5322  }
5323  }
5324  else
5325  {
5326  ExitPos = 0;
5327  }
5328  }
5329  else
5330  {
5331  ExitPos = Track->GetNonPointsOppositeLinkPos(EntryPos);
5332  }
5333  NextTrackVectorPosition = Track->TrackElementAt(392, CurrentTrackVectorPosition).Conn[ExitPos];
5334  NextEntryPos = Track->TrackElementAt(393, CurrentTrackVectorPosition).ConnLinkPos[ExitPos];
5335  CurrentTrackVectorPosition = NextTrackVectorPosition;
5336  EntryPos = NextEntryPos;
5337  ElementCount++;
5338  if(ElementCount > 1000)
5339  {
5340  ReturnVal = 4;
5341  break;
5342  }
5343  }
5344  if(ReturnVal == 1)
5345  {
5346  Utilities->CallLogPop(708);
5347  return(false);
5348  }
5349  if(ReturnVal == 2)
5350  {
5351  Utilities->CallLogPop(709);
5352  return(true);
5353  }
5354  if(ReturnVal == 3)
5355  {
5356  Utilities->CallLogPop(946);
5357  return(true);
5358  }
5359  if(ReturnVal == 4)
5360  {
5361  Utilities->CallLogPop(947);
5362  return(true);
5363  }
5364  else
5365  {
5366  throw Exception("Error - failed to set ReturnVal in ClearToNextSignal()");
5367  }
5368 }
5369 
5370 // ---------------------------------------------------------------------------
5371 
5373 /*
5374  Check whether calling-on conditions met - a) approaching train has stopped at a signal but not at a location;
5375  b) if there is a facing train at the station, it is being held appropriately (must be awaiting a join (Fjo or jbo) or a
5376  change of direction (cdt), remaining here (Frh), or under signaller control);
5377  c) at least 1 platform available for the approaching train; d) points (if any) set for direct route into platform;
5378  e) approaching train is to stop at station; f) no more facing signals between train and platform; g) [dropped g]
5379  h) train in front preventing route being set far enough to release stop signal; i) train in front not exiting at continuation; j) signal must be within 4km of
5380  the stop platform; k) [dropped (k), now can set a reoute or part route into platform so can set points more easily.] l) no existing route conflicts with the route into the platform,
5381  m) not failed or stopped without power
5382  If all OK & route or part route not already set then set an unrestricted route into the station (just to the first platform), to prevent point changing or other route conflicts - if a partial route set than can still
5383  change points outside the route or have a route conflict if another route is set.
5384 */{
5385  if(Track->RouteFlashFlag || TrainFailed || StoppedWithoutPower) //failed & no power conditions added at v2.10.0
5386  {
5387  return(false); // don't want to create a new route from the stop signal if one is already in construction & can't call on if failed or no power
5388  }
5389  // some of the callingon route elements may be involved
5390  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CallingOnAllowed" + "," + HeadCode);
5391  bool PlatformFoundFlag = false, StopRequired = false, SkipRouteCheck = false, RouteOrPartRouteSet = false; // last added at v1.2.0
5392  int CurrentTrackVectorPosition = LeadElement, NextTrackVectorPosition, ElementNumber = 0, Distance = 0;
5393  int RouteStartPosition;
5394  // this is the track vector position of the start element for the new unrestricted route - one past the stop signal
5395  int PlatformPosition;
5396  // the track vector position of the first stop platfrom
5397  int EntryPos = LeadEntryPos, ExitPos, NextEntryPos, RouteID;
5398  // not used here
5399  AnsiString LeadStationName = Track->TrackElementAt(395, LeadElement).ActiveTrackElementName; // still OK even if ""
5400  int LeadElementDistance = Track->TrackElementAt(1017, LeadElement).Length01; //added after 2.7.0 as don't want to add this to overall distance since train has already covered this distance
5401  // use Length01, may be wrong for points/crossovers/bridges but unlikely to occur in practice
5402  // must be stopped at a signal but not at a location & still in timetable (a)
5404  // no need to check for SignallerStopped as this function only called in Timetable mode
5405  {
5406  Utilities->CallLogPop(711);
5407  return(false);
5408  }
5409  while(true)
5410  {
5411  TTrackElement &CurrentTrackElement = Track->TrackElementAt(396, CurrentTrackVectorPosition);
5412  // don't look further than 4km ahead (j)
5413  if(Distance > (4000 + LeadElementDistance))
5414  {
5415  Utilities->CallLogPop(967);
5416  return(false);
5417  }
5418  // if find another train on an element in front, before find a valid platform, return false (c)
5419  if((CurrentTrackElement.TrainIDOnElement > -1) && (CurrentTrackElement.TrainIDOnElement != TrainID) && !PlatformFoundFlag)
5420  {
5421  Utilities->CallLogPop(713);
5422  return(false);
5423  }
5424  // if find another train in front when there is a valid platform (keep searching after find a platform as train may still
5425  // be facing later on)
5426  if((CurrentTrackElement.TrainIDOnElement > -1) && (CurrentTrackElement.TrainIDOnElement != TrainID) && PlatformFoundFlag) //<-can return true in error if other train on bridge
5427  { //on other track, but leave in as shouldn't cause
5428  // get LeadElement, if -1 return (could be exiting at continuation) (i) //any problems - see if anyone reports it and if
5429  TTrain OtherTrain = TrainController->TrainVectorAtIdent(12, CurrentTrackElement.TrainIDOnElement); //so try to correct
5430  if(OtherTrain.LeadElement == -1)
5431  {
5432  Utilities->CallLogPop(714);
5433  return(false);
5434  }
5435  // if a facing train then make sure it is awaiting a join (Fjo or jbo) or a change of direction (cdt), or remaining here (Frh) (b)
5436  if(OtherTrain.LeadElement == CurrentTrackVectorPosition)
5437  {
5438  AnsiString OtherCommand = OtherTrain.ActionVectorEntryPtr->Command;
5439  if((OtherCommand == "Fjo") || (OtherCommand == "jbo") || (OtherCommand == "cdt") || (OtherCommand == "Frh") ||
5440  (OtherTrain.TrainMode == Signaller))
5441  {
5442  break;
5443  }
5444  else
5445  {
5446  Utilities->CallLogPop(955);
5447  return(false);
5448  }
5449  }
5450  else // (h)
5451  {
5452  break;
5453  }
5454  }
5455  // if reach buffers or exit continuation return false (can set route)
5456  if(((CurrentTrackElement.TrackType == Buffers) || (CurrentTrackElement.TrackType == Continuation)) && (EntryPos == 1))
5457  {
5458  Utilities->CallLogPop(716);
5459  return(false);
5460  }
5461  // if reach forward signal (other than the one the train is waiting at) return false (can set route) (f)
5462  if((EntryPos < 2) && (CurrentTrackElement.Config[1 - EntryPos] == Signal) && (CurrentTrackVectorPosition != Track->TrackElementAt(404,
5464  {
5465  Utilities->CallLogPop(717);
5466  return(false);
5467  }
5468  // if reach a location that isn't in timetable return false - drop this as still can't set a route
5469 /*
5470  if((Track->TrackElementAt(405, CurrentTrackVectorPosition).ActiveTrackElementName != "") && (Track->TrackElementAt(406, CurrentTrackVectorPosition).ActiveTrackElementName != LeadStationName) &&
5471  (NameInTimetableBeforeCDT(14, Track->TrackElementAt(407, CurrentTrackVectorPosition).ActiveTrackElementName) == -1))
5472  {
5473  Utilities->CallLogPop(718);
5474  return false;
5475  }
5476 */
5477  // if reach a location that is in timetable set PlatformFoundFlag (but not if position is points set to diverge) (e)
5478  if((CurrentTrackElement.ActiveTrackElementName != "") && (CurrentTrackElement.ActiveTrackElementName != LeadStationName) &&
5479  (NameInTimetableBeforeCDT(15, CurrentTrackElement.ActiveTrackElementName, StopRequired) > -1))
5480  {
5481  if(StopRequired)
5482  {
5483  if((CurrentTrackElement.TrackType != Points) || ((CurrentTrackElement.TrackType == Points) && (CurrentTrackElement.Attribute == 0)))
5484  {
5485  if(!PlatformFoundFlag)
5486  {
5487  PlatformPosition = CurrentTrackVectorPosition;
5488  }
5489  // ensure this only set once at first valid platform position - the unrestricted route will end here
5490  PlatformFoundFlag = true;
5491  }
5492  }
5493  }
5494  // Drop this below - was to prevent call-on if front train had left the station. Criterion now is not that front
5495  // train has to be at station but that has to be before the next forward signal
5496 /*
5497  if((Track->TrackElementAt(411, CurrentTrackVectorPosition).ActiveTrackElementName == "") && (PlatformFoundFlag))
5498  {
5499  Utilities->CallLogPop(719);
5500  return false;
5501  }
5502 */
5503  // make sure points are followed correctly (d) & set ExitPos
5504  if(CurrentTrackElement.TrackType == Points)
5505  {
5506  if((EntryPos == 0) || (EntryPos == 2))
5507  {
5508  if(CurrentTrackElement.Attribute == 0)
5509  {
5510  ExitPos = 1;
5511  }
5512  else
5513  {
5514  ExitPos = 3;
5515  }
5516  }
5517  if(EntryPos == 1)
5518  {
5519  if(CurrentTrackElement.Attribute == 0)
5520  {
5521  ExitPos = 0;
5522  }
5523  else
5524  {
5525  Utilities->CallLogPop(720);
5526  return(false);
5527  }
5528  }
5529  if(EntryPos == 3)
5530  {
5531  if(CurrentTrackElement.Attribute == 1)
5532  {
5533  ExitPos = 0;
5534  }
5535  else
5536  {
5537  Utilities->CallLogPop(721);
5538  return(false);
5539  }
5540  }
5541  }
5542  else
5543  {
5544  ExitPos = Track->GetNonPointsOppositeLinkPos(EntryPos);
5545  }
5546  // check existing routes - if element forward of the signal (ElementNumber == 2) is AutoSignals then OK without further checks as this route must extend to
5547  // the next signal so must at least reach the station, also if have another route set (must be unrestricted) from either the stop signal or the element after it
5548  // to or towards the platform (& all points set correctly) then OK, otherwise reject if (1) there are any route elements already set from element
5549  // forward of element after the signal to & including the first platform element (covers crossover with other route set) or (2) a fouled diagonal (k)
5550  if(ElementNumber < 2)
5551  {
5552  SkipRouteCheck = true;
5553  }
5554  else
5555  {
5556  SkipRouteCheck = false;
5557  }
5558  if(ElementNumber == 1) // the stop signal
5559  {
5560  RouteStartPosition = CurrentTrackVectorPosition;
5561  }
5562 /*
5563  if(ElementNumber == 2)
5564  {
5565  if(AllRoutes->GetRouteTypeAndNumber(18, CurrentTrackVectorPosition, EntryPos, RouteID) == AllRoutes->AutoSigsRoute) AutoSigs = true;
5566  else AutoSigs = false;
5567  if(AllRoutes->GetRouteTypeAndNumber(25, CurrentTrackVectorPosition, EntryPos, RouteID) == AllRoutes->NotAutoSigsRoute) OtherFullRouteSet = true;
5568  }
5569 */
5570  if(ElementNumber > 1)
5571  {
5572  if(AllRoutes->GetRouteTypeAndNumber(26, CurrentTrackVectorPosition, EntryPos, RouteID) != AllRoutes->NoRoute)
5573  {
5574  RouteOrPartRouteSet = true;
5575  }
5576  else
5577  {
5578  RouteOrPartRouteSet = false;
5579  }
5580  }
5581  if(!SkipRouteCheck && !RouteOrPartRouteSet)
5582  {
5583  if(AllRoutes->TrackIsInARoute(16, CurrentTrackVectorPosition, EntryPos)) // must be a conflicting route
5584  {
5585  Utilities->CallLogPop(1859);
5586  return(false);
5587  }
5588  int ExitLink = CurrentTrackElement.Link[ExitPos];
5589  if((ExitLink == 1) || (ExitLink == 3) || (ExitLink == 7) || (ExitLink == 9))
5590  {
5591  if(AllRoutes->DiagonalFouledByRouteOrTrain(6, CurrentTrackElement.HLoc, CurrentTrackElement.VLoc, ExitLink))
5592  {
5593  Utilities->CallLogPop(1850);
5594  return(false);
5595  }
5596  }
5597  }
5598  // finished all checks, now update CurrentTrackVectorPosition & EntryPos for the next iteration
5599  if(EntryPos < 2)
5600  {
5601  Distance += CurrentTrackElement.Length01;
5602  }
5603  else
5604  {
5605  Distance += CurrentTrackElement.Length23;
5606  }
5607  NextTrackVectorPosition = CurrentTrackElement.Conn[ExitPos];
5608  NextEntryPos = CurrentTrackElement.ConnLinkPos[ExitPos];
5609  CurrentTrackVectorPosition = NextTrackVectorPosition;
5610  EntryPos = NextEntryPos;
5611  ElementNumber++;
5612  } // while(true)
5613 
5614  // if all OK & autosigs route not already set then set an unrestricted route into the station (just to the first platform)
5615  // from the stop signal (note that it may be last in an autosigs route)
5616  // a single element route at the stop signal should have been removed prior to this function being called (that called before
5617  // this in ClockTimer2)
5618 
5619  // now add elements to the CallonVector
5620  TAllRoutes::TCallonEntry CallonEntry(RouteOrPartRouteSet, RouteStartPosition, PlatformPosition);
5621 
5622  AllRoutes->CallonVector.push_back(CallonEntry);
5623  Utilities->CallLogPop(1860);
5624  return(true); // return false if fail to set route for any reason
5625 }
5626 
5627 // ---------------------------------------------------------------------------
5628 /*
5629  bool TTrain::TimetableFinished()
5630  {
5631  if((ActionVectorEntryPtr == TrainDataEntryPtr->ActionVector.end()) || (ActionVectorEntryPtr->FormatType == Repeat))//past all actions
5632  {
5633  return true;
5634  }
5635  return false;
5636  }
5637 */
5638 // ---------------------------------------------------------------------------
5639 
5640 AnsiString TTrain::GetTrainHeadCode(int Caller)
5641 
5642 {
5643  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetTrainHeadCode" + "," + HeadCode);
5644  AnsiString RepeatHeadCode = TrainController->GetRepeatHeadCode(0, HeadCode, RepeatNumber, IncrementalDigits);
5645 
5646  Utilities->CallLogPop(1452);
5647  return(RepeatHeadCode);
5648 }
5649 
5650 // ---------------------------------------------------------------------------
5651 
5652 TDateTime TTrain::GetTrainTime(int Caller, TDateTime Time)
5653 {
5654  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetTrainTime," + Utilities->Format96HHMMSS(Time));
5655  TDateTime RepeatTime = TrainController->GetRepeatTime(1, Time, RepeatNumber, IncrementalMinutes);
5656 
5657  Utilities->CallLogPop(1453);
5658  return(RepeatTime);
5659 }
5660 
5661 // ---------------------------------------------------------------------------
5662 
5663 bool TTrain::IsThereAnAdjacentTrain(int Caller, TTrain *&TrainToBeJoinedBy)
5664 {
5665  // Used to check for a stopped adjacent train for use in PopUp menu //new at v2.4.0
5666  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsThereAnAdjacentTrain" + "," + HeadCode);
5667  // check if there's a stopped adjacent train, if there is but not under sig control give a message in calling function
5668  // first check that train is fully on the railway
5669  bool FrontValid = false, RearValid = false;
5670  TTrackElement FrontAdjacentTrackElement, RearAdjacentTrackElement;
5671 
5672  if((LeadElement == -1) || (MidElement == -1))
5673  {
5674  TrainToBeJoinedBy = NULL;
5675  Utilities->CallLogPop(2131);
5676  return(false);
5677  }
5679  {
5680  FrontAdjacentTrackElement = Track->TrackElementAt(965, (Track->TrackElementAt(966, LeadElement).Conn[LeadExitPos]));
5681  FrontValid = true;
5682  }
5684  {
5685  RearAdjacentTrackElement = Track->TrackElementAt(968, (Track->TrackElementAt(969, MidElement).Conn[MidEntryPos]));
5686  RearValid = true;
5687  }
5688  int TrainToBeJoinedByID = -1;
5689 
5690  // first check if on a 2-track element & select correct ID number if so
5691  if(FrontValid)
5692  {
5693  if(FrontAdjacentTrackElement.TrackType == Bridge)
5694  {
5696  {
5697  TrainToBeJoinedByID = FrontAdjacentTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit23;
5698  }
5699  else
5700  {
5701  TrainToBeJoinedByID = FrontAdjacentTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit01;
5702  }
5703  }
5704  else
5705  {
5706  TrainToBeJoinedByID = FrontAdjacentTrackElement.TrainIDOnElement;
5707  }
5708  }
5709  if((TrainToBeJoinedByID < 0) && RearValid)
5710  {
5711  // first check if on a 2-track element & select correct ID number if so
5712  if(RearAdjacentTrackElement.TrackType == Bridge)
5713  {
5715  {
5716  TrainToBeJoinedByID = RearAdjacentTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit23;
5717  }
5718  else
5719  {
5720  TrainToBeJoinedByID = RearAdjacentTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit01;
5721  }
5722  }
5723  else
5724  {
5725  TrainToBeJoinedByID = RearAdjacentTrackElement.TrainIDOnElement;
5726  }
5727  }
5728  if(TrainToBeJoinedByID < 0) // no adjacent train
5729  {
5730  TrainToBeJoinedBy = NULL;
5731  Utilities->CallLogPop(2132);
5732  return(false);
5733  }
5734  TrainToBeJoinedBy = &(TrainController->TrainVectorAtIdent(44, TrainToBeJoinedByID));
5735  if(!TrainToBeJoinedBy->Stopped())
5736  {
5737  TrainToBeJoinedBy = NULL;
5738  Utilities->CallLogPop(2133);
5739  return(false);
5740  }
5741  Utilities->CallLogPop(2134);
5742  return(true);
5743 }
5744 
5745 // ---------------------------------------------------------------------------
5746 
5747 void TTrain::LogAction(int Caller, AnsiString OwnHeadCode, AnsiString OtherHeadCode, TActionType ActionType, AnsiString LocationName, AnsiString SplitDistribution,
5748  TDateTime TimetableNonRepeatTime, bool Warning)
5749 /*
5750  Time = timetable time, the time adjustments for repeat trains is carried out internally
5751  Not all messages need this, if not needed a dummy value is required but not used
5752 
5753  Arrive: 06:05:40: 2F46 arrived at Old Street 1 minute late
5754  Pass: 06:05:40: 2F46 passed Old Street 1 minute late
5755  Terminate: 06:05:40: 2F46 terminated at Old Street 1 minute late <-- sent from RemainHere as LogAction not called for terminate
5756  //NB for Frh just give terminated message but without event time - don't use this function
5757  Depart: 06:05:15: 3F43 departed from Essex Road 2 minutes late
5758  Create: 06:05:40: 2F46 created at Old Street 1 minute late
5759  Enter: 06:05:40: 2F46 entered railway at Old Street 1 minute late
5760  Leave: 06:05:40: 2F46 left railway at 57-N4 1 minute late
5761  FrontSplit: 06:05:40: 2F46 split mass%-Power% = 10-50 from front to 3D54 at Old Street 1 minute late
5762  RearSplit: 06:05:40: 2F46 split mass%-Power% = 10-50 from rear to 3D54 at Old Street 1 minute late
5763  JoinedByOther: 06:05:40: 2F46 joined by 3D54 at Old Street 1 minute late
5764  ChangeDirection: 06:05:40: 2F46 changed direction at Old Street 1 minute late
5765  ChangeDescription: 06:05:40: 2F46 changed its description to 'NewDescription' at Old Street 1 minute late
5766  ChangeMaxSpeed: 06:05:40: 2F46 changed its maximum speed to 'NewMaxSpeed' at Old Street 1 minute late
5767  NewService: 06:05:40: 2F46 became new service 3D54 at Old Street 1 minute late
5768  TakeManualControl: 06:05:40: 2F46 taken under signaller control at Old Street
5769  RestoreTimetableControl: 06:05:40: 2F46 restored to timetable control at Old Street
5770  RemoveTrain: 06:05:40: 2F46 REMOVED FROM RAILWAY DUE TO CRASH at Old Street
5771  RemoveTrain: 06:05:40: 2F46 REMOVED FROM RAILWAY DUE TO DERAILMENT at Old Street
5772  RemoveTrain: 06:05:40: 2F46 REMOVED FROM RAILWAY at Old Street
5773  SignallerMoveForwards 06:05:40: 2F46 received signaller authority to proceed
5774  SignallerChangeDirection 06:05:40: 2F46 changed direction under signaller control at Old Street
5775  SignallerPassRedSignal 06:05:40: 2F46 received signaller authority to pass stop signal
5776  SignallerJoin 06:05:40: 2F46 joined under signaller control by 3D54 at Old Street //new at v2.4.0
5777  TrainFailure 06:05:40: 2F46 suffered an onboard power failure at Old Street //new at v2.4.0
5778  RepairFailedTrain 06:05:40: 2F46 failure repaired at Old Street //new at v2.4.0
5779  SignallerControlStop 06:05:40: 2F46 received signaller instruction to stop
5780  SignallerStop 06:05:40: 2F46 stopped on signaller command
5781  SignallerLeave: 06:05:40: 2F46 left railway under signaller control at 57-N4
5782  SignallerStepForward: 06:05:40: 2F46 received signaller authority to step forward
5783 */{
5784  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LogAction," + OwnHeadCode + "," + OtherHeadCode + "," +
5785  AnsiString(ActionType) + "," + LocationName + "," + HeadCode);
5786  AnsiString BaseLog = "", WarningBaseLog = "", ReminderBaseLog = "", PerfLog = "", ActionLog = "";
5787  int IntMinsLate = 0;
5788  bool TTEvent = false; //indicates a timetabled event, prevents reminders for non-tt events where these set for next tt event
5789  //don't need it for warnings as these passed in from appropriate calling functions
5790  // need to set it in case MinsLate == 0, since it isn't tested for that
5791  if(ActionType == Arrive)
5792  {
5793  ActionLog = " arrived at ";
5794  TTEvent = true;
5795  }
5796  if(ActionType == Terminate) //redundant as Logaction not called for terminate - RemainHere deals with logging for terminate
5797  {
5798  if(TerminatedMessageSent) // to avoid it being sent twice
5799  {
5800  Utilities->CallLogPop(1104);
5801  return;
5802  }
5803  ActionLog = " terminated at ";
5804  TTEvent = true;
5805  TerminatedMessageSent = true;
5806  }
5807  if(ActionType == Depart)
5808  {
5809  ActionLog = " departed from ";
5810  TTEvent = true;
5811  }
5812  if(ActionType == Pass)
5813  {
5814  ActionLog = " passed ";
5815  TTEvent = true;
5816  }
5817  if(ActionType == Create)
5818  {
5819  ActionLog = " created at ";
5820  }
5821  if(ActionType == Enter)
5822  {
5823  ActionLog = " entered railway at ";
5824  }
5825  if(ActionType == ChangeDescription)
5826  {
5827  ActionLog = " changed its description to '" + Description + "' at "; //change to train description at v2.16.1
5828  TTEvent = true;
5829  }
5830  if(ActionType == ChangeMaxSpeed)
5831  {
5832  ActionLog = " changed its maximum speed to " + ActionVectorEntryPtr->NewMaxSpeed + " at "; //change to train max speed at v2.21.0
5833  TTEvent = true;
5834  }
5835  if(ActionType == Leave)
5836  {
5837  ActionLog = " left railway at ";
5838  TTEvent = true;
5839  }
5840  if(ActionType == FrontSplit)
5841  {
5842  ActionLog = " split mass%-Power% = " + SplitDistribution + " from front to ";
5843  TTEvent = true;
5844  }
5845  if(ActionType == RearSplit)
5846  {
5847  ActionLog = " split mass%-Power% = " + SplitDistribution + " from rear to ";
5848  TTEvent = true;
5849  }
5850  if(ActionType == JoinedByOther)
5851  {
5852  ActionLog = " joined by ";
5853  TTEvent = true;
5854  }
5855  if(ActionType == ChangeDirection)
5856  {
5857  ActionLog = " changed direction at ";
5858  TTEvent = true;
5859  }
5860  if(ActionType == NewService)
5861  {
5862  ActionLog = " became new service ";
5863  TTEvent = true;
5864  }
5865  if(ActionType == TakeSignallerControl)
5866  {
5867  ActionLog = " taken under signaller control at ";
5868  }
5869  if(ActionType == RestoreTimetableControl)
5870  {
5871  ActionLog = " restored to timetable control at ";
5872  }
5873  if(ActionType == RemoveTrain)
5874  {
5875  if(Crashed)
5876  {
5877  ActionLog = " REMOVED FROM RAILWAY DUE TO CRASH at ";
5878  }
5879  else if(Derailed)
5880  {
5881  ActionLog = " REMOVED FROM RAILWAY DUE TO DERAILMENT at ";
5882  }
5883  else
5884  {
5885  ActionLog = " REMOVED FROM RAILWAY at ";
5886  }
5887  }
5888  if(ActionType == SignallerMoveForwards)
5889  {
5890  ActionLog = " received signaller authority to proceed";
5891  }
5892  if(ActionType == SignallerStepForward)
5893  {
5894  ActionLog = " received signaller authority to step forward";
5895  }
5896  if(ActionType == SignallerChangeDirection)
5897  {
5898  ActionLog = " changed direction under signaller control at ";
5899  }
5900  if(ActionType == SignallerPassRedSignal)
5901  {
5902  ActionLog = " received signaller authority to pass stop signal";
5903  }
5904  if(ActionType == SignallerControlStop)
5905  {
5906  ActionLog = " received signaller instruction to stop";
5907  }
5908  if(ActionType == SignallerStop)
5909  {
5910  ActionLog = " stopped on signaller instruction ";
5911  }
5912  if(ActionType == SignallerJoin)
5913  {
5914  ActionLog = " joined under signaller control by ";
5915  }
5916  if(ActionType == TrainFailure)
5917  {
5918  ActionLog = " suffered an onboard power failure at ";
5919  }
5920  if(ActionType == RepairFailedTrain)
5921  {
5922  ActionLog = " failure repaired at ";
5923  }
5924  if(ActionType == SignallerLeave)
5925  {
5926  ActionLog = " left railway under signaller control at ";
5927  }
5928  if(OtherHeadCode != "")
5929  {
5930  OtherHeadCode += " at ";
5931  }
5932  TDateTime ActualTime = TrainController->TTClockTime;
5933 
5934  if(Warning)
5935  {
5936  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " WARNING: " + HeadCode + ActionLog + OtherHeadCode + LocationName;
5937  WarningBaseLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + ActionLog + OtherHeadCode + LocationName; //added time at v2.13.0
5938  }
5939  else //added at v2.19.0
5940  {
5941  if(TTEvent && ((ActionVectorEntryPtr->Reminder == 1) || (ActionVectorEntryPtr->Reminder == 4)))
5942  {
5943  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " REMINDER: " + HeadCode + ActionLog + OtherHeadCode + LocationName;
5944  ReminderBaseLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + ActionLog + OtherHeadCode + LocationName; //added time at v2.13.0
5945  }
5946  else if(TTEvent && (ActionVectorEntryPtr->Reminder == 2) && (ActionLog == " departed from ")) //depart only
5947  {
5948  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " REMINDER: " + HeadCode + ActionLog + OtherHeadCode + LocationName;
5949  ReminderBaseLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + ActionLog + OtherHeadCode + LocationName; //added time at v2.13.0
5950  }
5951  else if(TTEvent && (ActionVectorEntryPtr->Reminder == 3) && (ActionLog == " arrived at ")) //arrive only
5952  {
5953  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " REMINDER: " + HeadCode + ActionLog + OtherHeadCode + LocationName;
5954  ReminderBaseLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + ActionLog + OtherHeadCode + LocationName; //added time at v2.13.0
5955  }
5956  else
5957  {
5958  BaseLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + ActionLog + OtherHeadCode + LocationName;
5959  }
5960  }
5961 
5962  bool TimePerformance = true;
5963 
5964  if((ActionType == TakeSignallerControl) || (ActionType == RestoreTimetableControl) || (ActionType == RemoveTrain) || (ActionType == SignallerMoveForwards)
5965  || (ActionType == SignallerChangeDirection) || (ActionType == SignallerPassRedSignal) || (ActionType == SignallerControlStop) ||
5966  (ActionType == SignallerStop) || (ActionType == SignallerLeave) || (ActionType == SignallerStepForward) || (ActionType == SignallerJoin) ||
5967  (ActionType == TrainFailure) || (ActionType == RepairFailedTrain))
5968  // SignallerJoin & RepairFailedTrain new at v2.4.0
5969  {
5970  TimePerformance = false;
5971  }
5972  if(TimePerformance)
5973  {
5974  double MinsLate = ((double)(ActualTime - GetTrainTime(1, TimetableNonRepeatTime))) * 1440;
5975  MinsDelayed = float(MinsLate);
5976  if(ActionType == Pass) //added at v2.9.2 to prevent time to act increasing suddenly for early pass times then becoming 'NOW' when stops at signal
5977  {
5978  MinsDelayed = 0;
5979  }
5980  // new v2.2.0 for OpActionPanel, can be positive or negative
5981  if(ActionType == Arrive)
5982  {
5984  }
5985  // since train has just arrived this value is the
5986  // recoverable time available at this stop, so reduce MinsDelayed by this amount to prevent it being
5987  // subtracted from later stop recoverable times.
5988  if(MinsLate < 0)
5989  {
5990  IntMinsLate = int(ceil(MinsLate));
5991  }
5992  if(MinsLate > 0)
5993  {
5994  IntMinsLate = int(floor(MinsLate));
5995  }
5996  if(IntMinsLate == 0)
5997  {
5998  PerfLog = " on time";
5999  }
6000  else if(IntMinsLate == 1)
6001  {
6002  PerfLog = " 1 minute late";
6003  }
6004  else if(IntMinsLate == -1)
6005  {
6006  PerfLog = " 1 minute early";
6007  }
6008  else if(IntMinsLate > 1)
6009  {
6010  PerfLog = " " + AnsiString(IntMinsLate) + " minutes late";
6011  }
6012  else if(IntMinsLate < -1)
6013  {
6014  int PosIntMinsLate = -IntMinsLate;
6015  PerfLog = " " + AnsiString(PosIntMinsLate) + " minutes early";
6016  }
6017  if(LocationName.Pos('-') > 0)
6018  {
6019  PerfLog = "," + PerfLog;
6020  // if a position add a comma to separate vertical position number from number of minutes (better appearance)
6021  }
6022  PerfLogForm->PerformanceLog(0, BaseLog + PerfLog);
6023  }
6024  else
6025  {
6026  PerfLogForm->PerformanceLog(1, BaseLog);
6027  }
6028  if(Warning)
6029  {
6030  Display->WarningLog(0, WarningBaseLog);
6031  }
6032  if(ReminderBaseLog != "") //added at v2.19.0
6033  {
6034  Display->WarningLog(24, ReminderBaseLog);
6035  ReminderBaseLog = ""; //reset to null
6036  ActionVectorEntryPtr->Reminder = 0; //to prevent reminders for repeats
6037  }
6038  // update statistics
6039  if((ActionType == Arrive) && (IntMinsLate == 0))
6040  {
6042  }
6043  else if((ActionType == Arrive) && (IntMinsLate > 0))
6044  {
6046  TrainController->TotLateArrMins += IntMinsLate;
6047  }
6048  else if((ActionType == Arrive) && (IntMinsLate < 0))
6049  {
6051  TrainController->TotEarlyArrMins += abs(IntMinsLate);
6052  }
6053 
6054  else if((ActionType == Pass) && (IntMinsLate == 0))
6055  {
6057  }
6058  else if((ActionType == Pass) && (IntMinsLate > 0))
6059  {
6061  TrainController->TotLatePassMins += IntMinsLate;
6062  }
6063  else if((ActionType == Pass) && (IntMinsLate < 0))
6064  {
6066  TrainController->TotEarlyPassMins += abs(IntMinsLate);
6067  }
6068 
6069  else if((ActionType == Leave) && (IntMinsLate == 0)) //new at v2.9.1 as had been omitted in error earlier
6070  {
6072  }
6073  else if((ActionType == Leave) && (IntMinsLate > 0))
6074  {
6076  TrainController->TotLateExitMins += IntMinsLate;
6077  }
6078  else if((ActionType == Leave) && (IntMinsLate < 0))
6079  {
6081  TrainController->TotEarlyExitMins += abs(IntMinsLate);
6082  }
6083 
6084  else if((ActionType == Depart) && (IntMinsLate == 0)) //can't depart early
6085  {
6087  }
6088  else if((ActionType == Depart) && (IntMinsLate > 0))
6089  {
6091  TrainController->TotLateDepMins += IntMinsLate;
6092  }
6093  Utilities->CallLogPop(968);
6094 }
6095 
6096 // ---------------------------------------------------------------------------
6097 
6098 void TTrain::TrainHasFailed(int Caller)
6099 {
6100  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainHasFailed," + HeadCode);
6101  if(Crashed || Derailed || DerailPending)
6102  {
6103  TrainFailurePending = false;
6104  Utilities->CallLogPop(2135);
6105  return;
6106  }
6107  AnsiString LocName = "";
6108 
6109  if(LeadElement > -1)
6110  {
6112  }
6113  if((LocName == "") && (MidElement > -1))
6114  {
6116  }
6117  if((LocName == "") && LeadElement > -1)
6118  {
6119  LocName = Track->TrackElementAt(974, LeadElement).ElementID;
6120  }
6121  if((LocName == "") && (MidElement > -1))
6122  {
6123  LocName = Track->TrackElementAt(975, MidElement).ElementID;
6124  }
6125  TrainController->StopTTClockMessage(81, HeadCode + " has suffered an onboard power failure at " + LocName);
6126  TrainFailed = true;
6127  TrainFailurePending = false;
6128  CallingOnFlag = false; //added at v2.10.0
6130  PowerAtRail = 0.08;
6131  AValue = sqrt(2 * PowerAtRail / Mass);
6133  // TrainFailed only called when PlotElements properly set to Lead, Mid & Lag elements
6134  if(Stopped())
6135  {
6136  EntrySpeed = 0;
6137  ExitSpeedHalf = 0;
6138  ExitSpeedFull = 0;
6139  MaxExitSpeed = 0;
6140  BrakeRate = 0;
6141  StoppedWithoutPower = true;
6142  }
6144  LogAction(33, HeadCode, "", TrainFailure, LocName, "", TDateTime(0), true);
6145  // true for warning, TDateTime not used
6146  Utilities->CallLogPop(2136);
6147 }
6148 
6149 // ---------------------------------------------------------------------------
6150 
6151 void TTrain::FrontTrainSplit(int Caller) //Major rewrite at v2.18.0 using new ThisLocationLongEnoughForSplit
6152 {
6153 /*
6154  Split logic for is:- the final 4 train positions must overlap with the original train, & final 4 positions
6155  will maximise the number at the location. Note that this function isn't sophisticated enough to account for trains already at the
6156  location in determining the 4 positions, and will give a failure message if a train obstructs any of the 4 positions. In these
6157  circumstances the other train will need to be moved sufficiently away to release all 4 positions, then the train will split.
6158 */
6159  TrainController->LogEvent("" + AnsiString(Caller) + ",FrontTrainSplit" + "," + HeadCode);
6160  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",FrontTrainSplit" + "," + HeadCode);
6161 
6162 /* restriction removed at v2.19.0
6163  if(PowerAtRail < 1)
6164  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can split when power restored
6165  {
6166  if(!ZeroPowerNoFrontSplitMessage)
6167  {
6168  TrainController->StopTTClockMessage(82, HeadCode + ": A train without power can't split");
6169  }
6170  ZeroPowerNoFrontSplitMessage = true;
6171  Utilities->CallLogPop(2137);
6172  return;
6173  }
6174 */
6175 
6176  AnsiString LocationName = Track->TrackElementAt(555, LeadElement).ActiveTrackElementName;
6177 
6178  if(LocationName == "")
6179  {
6180  LocationName = Track->TrackElementAt(837, MidElement).ActiveTrackElementName;
6181  }
6182  int RearTrainRearPos, RearTrainFrontPos, RearTrainExitPos;
6183  int FrontTrainRearPos, FrontTrainFrontPos;
6185 
6186  if(LocationName == "")
6187  {
6188  throw Exception("Error - LocationName not set in FrontTrainSplit");
6189  }
6190  // if message given call at ~5 sec intervals in case train repositioned
6191 
6192  bool TemporaryDelay = false;
6194  MidEntryPos, FrontTrainFrontPos, FrontTrainRearPos, RearTrainFrontPos, RearTrainRearPos, TemporaryDelay))
6195  {
6197  {
6199  {
6200  TrainController->StopTTClockMessage(151, HeadCode + " failed to split - location too short at " + LocationName + ", reposition train if possible.");
6201  TrainController->LogActionError(6, HeadCode, "", FailLocTooShort, LocationName);
6203  }
6204  }
6205  Utilities->CallLogPop(1009); //these were inside above bracket & caused own detected fault on second access as didn't return, moved here at v2.19.0
6206  return; //this has been here for a very long time (at least from 2.0.0) but never reported! Either never reached this point or
6207  } //found & not reported. Same for RearTrainSplit
6208 
6209  if(TemporaryDelay)
6210  {
6212  Utilities->CallLogPop(2683);
6213  return;
6214  }
6215 
6216 //it is long enough for split
6217  AnsiString SplitTrainFixedDescription = ActionVectorEntryPtr->LinkedTrainEntryPtr->FixedDescription; //save these for new train before ActionVectorEntryPtr incremented
6218  AnsiString SplittingTrainDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
6219  bool SplitTrainExplicitDescription = ActionVectorEntryPtr->LinkedTrainEntryPtr->ExplicitDescription;
6221 
6222  UnplotTrain(0);
6223  StartSpeed = 0;
6224  RearStartElement = RearTrainRearPos; //this is for the current train, not the new train which will attach to the front of this train
6225  for(int x = 0; x < 4; x++)
6226  {
6227  if(Track->TrackElementAt(1664, RearStartElement).Conn[x] == RearTrainFrontPos)
6228  {
6229  RearStartExitPos = x;
6230  }
6231  }
6232  StoppedAtLocation = true;
6233  if((PowerAtRail < 1) && EntrySpeed < 1) // added at v2.4.0
6234  {
6235  StoppedWithoutPower = true;
6236  }
6237  PlotStartPosition(3);
6240 // ActionVectorEntryPtr++; moved lower down at v2.15.0 because of new section below
6242 
6243  //new at v2.15.0 for unequal split in mass & power
6244  int NewTrainMass;
6245  double NewTrainPowerAtRail;
6247  {
6248  int pos = ActionVectorEntryPtr->SplitDistribution.Pos('-');
6249  int MassPercent = ActionVectorEntryPtr->SplitDistribution.SubString(1, pos - 1).ToInt(); //validity checked during validation
6250  int PowerPercent = ActionVectorEntryPtr->SplitDistribution.SubString(pos + 1, ActionVectorEntryPtr->SplitDistribution.Length() - pos).ToInt();
6251  NewTrainMass = Mass * double(MassPercent)/100.0;
6252  Mass = Mass - NewTrainMass;
6253  NewTrainPowerAtRail = PowerAtRail * double(PowerPercent)/100.0;
6254  if(NewTrainPowerAtRail == 0)
6255  {
6256  NewTrainPowerAtRail = 0.08; //min value represents 0
6257  }
6258  PowerAtRail = PowerAtRail - NewTrainPowerAtRail;
6259  AValue = sqrt(2 * PowerAtRail / Mass);
6260  }
6261  else // same Mass, MaxBrakeRate & PowerAtRail as this train's halved values, and same MaxRunningSpeed as this train
6262  {
6263  Mass = Mass / 2;
6264  NewTrainMass = Mass;
6265  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).Mass = Mass;
6266  // MaxBrakeRate = MaxBrakeRate / 2; this was wrong - want brake rate to stay the same, brake force is halved but that not a train parameter
6267  // and when needed it's calculated from rate & mass - changed at v2.15.0
6268  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).MaxBrakeRate = MaxBrakeRate;
6269  PowerAtRail = PowerAtRail / 2;
6270  NewTrainPowerAtRail = PowerAtRail;
6271  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).PowerAtRail = PowerAtRail;
6272  AValue = sqrt(2 * PowerAtRail / Mass);
6273  // shouldn't change but include in case not set earlier
6274  }
6275 
6276  TActionEventType EventType = NoEvent;
6277  ActionVectorEntryPtr++; //moved here at v2.18.0 to give more chances to split in case points set wrongly initially, also when AddTrain TrainVector
6278  //may be repositioned so all references to this train may be invalid
6279 
6280  if(!TrainController->AddTrain(0, FrontTrainRearPos, FrontTrainFrontPos, OtherHeadCode, 0, NewTrainMass, MaxRunningSpeed, MaxBrakeRate, NewTrainPowerAtRail,
6281  "Timetable", LinkedTrainEntryPtr, RepeatNumber, IncrementalMinutes, IncrementalDigits, SignallerMaxSpeed, false, EventType))
6282  // false for SignallerControl
6283  {
6284  Utilities->CallLogPop(1721); // EventType not used here
6285  // if fails either a throw will have been sent in AddTrain or start position failed prob because of
6286  // another train, in which case a message will have been sent to the perf log, also might well clear later
6287  // when other train moves away
6288  return;
6289  }
6290 
6291  TrainController->TrainVector.back().Description = SplitTrainFixedDescription; //added at v2.16.1, new train takes description from its TrainDataEntry
6292  if(!SplitTrainExplicitDescription) //new at v2.15.0 see above
6293  {
6294 // OldActionVectorEntryPtr->LinkedTrainEntryPtr->Description = OriginalDescription; dropped at v2.16.1
6295  TrainController->TrainVector.back().Description = SplittingTrainDescription; //else takes it from this train's description
6296  }
6297 
6298  // Note data in 'this' now probably invalid as there has been a new addition to the TrainVector, so the train is likely to have a new address, hence make no more changes for the current train
6299  // see mods in UpdateTrain for v1.3.2
6300  TrainController->TrainAdded = true;
6301 
6302  TTrainOperatingData &TTOD = LinkedTrainEntryPtr->TrainOperatingDataVector.at(RepeatNumber); // this is for the newly created train
6303 
6304  TTOD.TrainID = TrainController->TrainVector.back().TrainID;
6305  TTOD.RunningEntry = Running;
6306  Utilities->CallLogPop(998);
6307 }
6308 
6309 // ---------------------------------------------------------------------------
6310 
6311 void TTrain::RearTrainSplit(int Caller) //Major rewrite at v2.18.0 using new ThisLocationLongEnoughForSplit
6312 {
6313 /*
6314  Split logic for is:- the final 4 train positions must overlap with the original train, & final 4 positions
6315  will maximise the number at the location. Note that this function isn't sophisticated enough to account for trains already at the
6316  location in determining the 4 positions, and will give a failure message if a train obstructs any of the 4 positions. In these
6317  circumstances the other train will need to be moved sufficiently away to release all 4 positions, then the train will split.
6318 */
6319  TrainController->LogEvent("" + AnsiString(Caller) + ",RearTrainSplit" + "," + HeadCode);
6320  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RearTrainSplit" + "," + HeadCode);
6321 
6322 /* restriction removed at v2.19.0
6323  if(PowerAtRail < 1)
6324  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can split when power restored
6325  {
6326  if(!ZeroPowerNoRearSplitMessage)
6327  {
6328  TrainController->StopTTClockMessage(176, HeadCode + ": A train without power can't split");
6329  }
6330  ZeroPowerNoRearSplitMessage = true;
6331  Utilities->CallLogPop(2685);
6332  return;
6333  }
6334 */
6335  AnsiString LocationName = Track->TrackElementAt(1676, LeadElement).ActiveTrackElementName;
6336 
6337  if(LocationName == "")
6338  {
6339  LocationName = Track->TrackElementAt(1677, MidElement).ActiveTrackElementName;
6340  }
6341  int RearTrainRearPos, RearTrainFrontPos;
6342  int FrontTrainRearPos, FrontTrainFrontPos, FrontTrainExitPos;
6344 
6345  if(LocationName == "")
6346  {
6347  throw Exception("Error - LocationName not set in RearTrainSplit");
6348  }
6349  // if message given call at ~5 sec intervals in case train repositioned
6350 
6351  bool TemporaryDelay = false;
6353  MidEntryPos, FrontTrainFrontPos, FrontTrainRearPos, RearTrainFrontPos, RearTrainRearPos, TemporaryDelay))
6354  {
6356  {
6358  {
6359  TrainController->StopTTClockMessage(177, HeadCode + " failed to split - location too short at " + LocationName + ", reposition train if possible.");
6360  TrainController->LogActionError(66, HeadCode, "", FailLocTooShort, LocationName);
6362  }
6363  }
6364  Utilities->CallLogPop(2686); //these were inside above bracket & caused own detected fault on second access as didn't return, moved here at v2.19.0
6365  return; //this has been here for a very long time (at least from 2.0.0) but never reported! Either never reached this point or
6366  } //found & not reported. Same for FrontTrainSplit
6367 
6368  if(TemporaryDelay)
6369  {
6371  Utilities->CallLogPop(2684);
6372  return;
6373  }
6374 
6375 //it is long enough for split
6376  AnsiString SplitTrainFixedDescription = ActionVectorEntryPtr->LinkedTrainEntryPtr->FixedDescription; //save these for new train before ActionVectorEntryPtr incremented
6377  AnsiString SplittingTrainDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
6378  bool SplitTrainExplicitDescription = ActionVectorEntryPtr->LinkedTrainEntryPtr->ExplicitDescription;
6380 
6381  UnplotTrain(11);
6382  StartSpeed = 0;
6383  RearStartElement = FrontTrainRearPos; //this is for the current train, not the new train which will attach to the rear of this train
6384  for(int x = 0; x < 4; x++)
6385  {
6386  if(Track->TrackElementAt(1665, RearStartElement).Conn[x] == FrontTrainFrontPos)
6387  {
6388  RearStartExitPos = x;
6389  }
6390  }
6391  StoppedAtLocation = true;
6392  if((PowerAtRail < 1) && EntrySpeed < 1) // added at v2.4.0
6393  {
6394  StoppedWithoutPower = true;
6395  }
6396  PlotStartPosition(12);
6399 // ActionVectorEntryPtr++; moved lower down at v2.15.0 because of new section below
6401 
6402  //new at v2.15.0 for unequal split in mass & power
6403  int NewTrainMass;
6404  double NewTrainPowerAtRail;
6406  {
6407  int pos = ActionVectorEntryPtr->SplitDistribution.Pos('-');
6408  int MassPercent = ActionVectorEntryPtr->SplitDistribution.SubString(1, pos - 1).ToInt(); //validity checked during validation
6409  int PowerPercent = ActionVectorEntryPtr->SplitDistribution.SubString(pos + 1, ActionVectorEntryPtr->SplitDistribution.Length() - pos).ToInt();
6410  NewTrainMass = Mass * double(MassPercent)/100.0;
6411  Mass = Mass - NewTrainMass;
6412  NewTrainPowerAtRail = PowerAtRail * double(PowerPercent)/100.0;
6413  if(NewTrainPowerAtRail == 0)
6414  {
6415  NewTrainPowerAtRail = 0.08; //min value represents 0
6416  }
6417  PowerAtRail = PowerAtRail - NewTrainPowerAtRail;
6418  AValue = sqrt(2 * PowerAtRail / Mass);
6419  }
6420  else // same Mass, MaxBrakeRate & PowerAtRail as this train's halved values, and same MaxRunningSpeed as this train
6421  {
6422  Mass = Mass / 2;
6423  NewTrainMass = Mass;
6424  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).Mass = Mass;
6425  // MaxBrakeRate = MaxBrakeRate / 2; this was wrong - want brake rate to stay the same, brake force is halved but that not a train parameter
6426  // and when needed it's calculated from rate & mass - changed at v2.15.0
6427  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).MaxBrakeRate = MaxBrakeRate;
6428  PowerAtRail = PowerAtRail / 2;
6429  NewTrainPowerAtRail = PowerAtRail;
6430  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).PowerAtRail = PowerAtRail;
6431  AValue = sqrt(2 * PowerAtRail / Mass);
6432  // shouldn't change but include in case not set earlier
6433  }
6434 
6435  TActionEventType EventType = NoEvent;
6436  ActionVectorEntryPtr++; //moved here at v2.18.0 to give more chances to split in case points set wrongly initially, also when AddTrain TrainVector
6437  //may be repositioned so all references to this train may be invalid
6438 
6439  if(!TrainController->AddTrain(4, RearTrainRearPos, RearTrainFrontPos, OtherHeadCode, 0, NewTrainMass, MaxRunningSpeed, MaxBrakeRate, NewTrainPowerAtRail,
6440  "Timetable", LinkedTrainEntryPtr, RepeatNumber, IncrementalMinutes, IncrementalDigits, SignallerMaxSpeed, false, EventType))
6441  // false for SignallerControl
6442  {
6443  Utilities->CallLogPop(2687); // EventType not used here
6444  // if fails either a throw will have been sent in AddTrain or start position failed prob because of
6445  // another train, in which case a message will have been sent to the perf log, also might well clear later
6446  // when other train moves away
6447  return;
6448  }
6449 
6450  TrainController->TrainVector.back().Description = SplitTrainFixedDescription; //added at v2.16.1, new train takes description from its TrainDataEntry
6451  if(!SplitTrainExplicitDescription) //new at v2.15.0 see above
6452  {
6453 // OldActionVectorEntryPtr->LinkedTrainEntryPtr->Description = OriginalDescription; dropped at v2.16.1
6454  TrainController->TrainVector.back().Description = SplittingTrainDescription; //else takes it from this train's description
6455  }
6456 
6457  // Note data in 'this' now probably invalid as there has been a new addition to the TrainVector, so the train is likely to have a new address, hence make no more changes for the current train
6458  // see mods in UpdateTrain for v1.3.2
6459  TrainController->TrainAdded = true;
6460 
6461  TTrainOperatingData &TTOD = LinkedTrainEntryPtr->TrainOperatingDataVector.at(RepeatNumber); // this is for the newly created train
6462 
6463  TTOD.TrainID = TrainController->TrainVector.back().TrainID;
6464  TTOD.RunningEntry = Running;
6465  Utilities->CallLogPop(2688);
6466 }
6467 
6468 // ---------------------------------------------------------------------------
6469 
6470 void TTrain::FinishJoin(int Caller)
6471 {
6472  if(FinishJoinLogSent == false)
6473  {
6474  TrainController->LogEvent("" + AnsiString(Caller) + ",FinishJoin" + "," + HeadCode);
6475  FinishJoinLogSent = true; // so don't keep logging this event
6476  // don't need to reset it to false after the event as the train is deleted
6477  }
6478  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",FinishJoin" + "," + HeadCode);
6479  if(TrainFailed)
6480  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can join when repaired) Can FinishJoin if zero power & not failed, as for empty stock
6481  {
6483  {
6484  TrainController->StopTTClockMessage(84, HeadCode + ": A failed train can't join another under timetable control");
6485  }
6487  Utilities->CallLogPop(2139);
6488  return;
6489  }
6490  if(TrainGone)
6491  // this means that the train has already joined the other & is awaiting deletion by TrainController
6492  // without this the 'waiting' message can be given since the other train's ActionVectorEntryPtr has moved
6493  // on from jbo & TrainToJoinIsAdjacent returns false
6494  {
6495  Utilities->CallLogPop(1035);
6496  return;
6497  }
6498  TTrain *TrainToJoin;
6500 
6501  if(!TrainToJoinIsAdjacent(0, TrainToJoin))
6502  {
6504  {
6505  // PerfLogForm->PerformanceLog(2, TrainController->TTClockTime.FormatString("hh:nn:ss") + ": " + HeadCode + " waiting to join " + JBOHeadCode + " at " + ActionVectorEntryPtr->LocationName);
6508  }
6509  Utilities->CallLogPop(1030);
6510  return; // keep this here in case need to add code before final return
6511  }
6512  // no need to clear error report flag here, cleared in jbo function
6513  // No need to set TimetableFinished, done in jbo function
6514  Utilities->CallLogPop(1031);
6515 }
6516 
6517 // ---------------------------------------------------------------------------
6518 
6519 void TTrain::JoinedBy(int Caller)
6520 {
6521  if(TrainController->OpTimeToActUpdateCounter == 0) //added at v2.13.2. Use OpTimeToActUpdateCounter for convenience so only issue the event log
6522  //once every second rather than many times. Can't use an event logged flag because there may
6523  //be several trains that are to be joined by others
6524  {
6525  TrainController->LogEvent("" + AnsiString(Caller) + "," + HeadCode + ",Waiting to be joined");
6526  }
6527  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",JoinedBy" + "," + HeadCode);
6528 
6529 /* restriction removed at v2.19.0
6530  if(PowerAtRail < 1)
6531  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can join when power restored)
6532  {
6533  if(!ZeroPowerNoJoinedByMessage)
6534  {
6535  TrainController->StopTTClockMessage(85, HeadCode + ": A train without power can't be joined by another under timetable control");
6536  }
6537  ZeroPowerNoJoinedByMessage = true;
6538  Utilities->CallLogPop(2140);
6539  return;
6540  }
6541 */
6542  TTrain *TrainToBeJoinedBy;
6544 
6545  if(!TrainToBeJoinedByIsAdjacent(0, TrainToBeJoinedBy))
6546  {
6548  {
6549  // PerfLogForm->PerformanceLog(3, TrainController->TTClockTime.FormatString("hh:nn:ss") + ": " + HeadCode + " waiting to be joined by " + FJOHeadCode + " at " + ActionVectorEntryPtr->LocationName);
6552  }
6553  LastActionDelayFlag = true;
6554  // need to update LastActionTime if this train first to arrive as need 30s after
6555  // both adjacent before the join
6556  Utilities->CallLogPop(1032);
6557  return;
6558  }
6559  // here when other train is adjacent
6561  {
6563  // need to update this as need 30s after both adjacent before the join
6564  LastActionDelayFlag = false;
6565  Utilities->CallLogPop(1033);
6566  return;
6567  }
6568  // here when other train is adjacent & 30 secs elapsed since both adjacent
6569 
6570  // set new values for mass etc
6571  if(MaxRunningSpeed > TrainToBeJoinedBy->MaxRunningSpeed)
6572  {
6573  MaxRunningSpeed = TrainToBeJoinedBy->MaxRunningSpeed;
6574  }
6575  double OtherBrakeForce = TrainToBeJoinedBy->MaxBrakeRate * TrainToBeJoinedBy->Mass;
6576  double OwnBrakeForce = MaxBrakeRate * Mass;
6577  double CombinedBrakeRate = (OtherBrakeForce + OwnBrakeForce) / (TrainToBeJoinedBy->Mass + Mass);
6578 
6579  Mass += TrainToBeJoinedBy->Mass;
6580  MaxBrakeRate = CombinedBrakeRate;
6581  PowerAtRail += TrainToBeJoinedBy->PowerAtRail;
6582  AValue = sqrt(2 * PowerAtRail / Mass);
6583 
6585  TrainToBeJoinedBy->TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).EventReported = NoEvent;
6586  TrainToBeJoinedBy->TimetableFinished = true;
6587  TrainToBeJoinedBy->TrainGone = true;
6588  TrainController->LogEvent("" + AnsiString(Caller) + "," + HeadCode + ",Joined By," + FJOHeadCode); //added at v2.13.2 to provide more information
6589  // this will cause other train to be deleted
6590  TrainToBeJoinedBy->JoinedOtherTrainFlag = true;
6594  Utilities->CallLogPop(1034);
6595 }
6596 
6597 // ---------------------------------------------------------------------------
6598 
6599 void TTrain::ChangeTrainDirection(int Caller, bool NoLogFlag)
6600 {
6601  TrainController->LogEvent("" + AnsiString(Caller) + ",ChangeTrainDirection" + "," + HeadCode);
6602  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ChangeTrainDirection" + "," + HeadCode);
6603 
6604  /* restriction removed at v2.19.0
6605  if(PowerAtRail < 1)
6606  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can change direction when power restored)
6607  {
6608  if(!ZeroPowerNoCDTMessage)
6609  {
6610  TrainController->StopTTClockMessage(86, HeadCode + ": A train without power can't change direction under timetable control");
6611  }
6612  ZeroPowerNoCDTMessage = true;
6613  Utilities->CallLogPop(2141);
6614  return;
6615  }
6616 */
6617  TColor TempColour = BackgroundColour;
6618 
6619  UnplotTrain(2);
6622  StartSpeed = 0;
6623  StoppedAtLocation = true;
6624  PlotStartPosition(1);
6625  PlotTrainWithNewBackgroundColour(27, TempColour, Display);
6626  // plot same as was - should always be pale green
6627  if(!NoLogFlag)
6628  {
6631  }
6633 
6634  //now erase a stub route if there is one, added at v2.5.1
6635  //first element of route is now immediately behind the train (i.e. next to MidElement)
6636  if(MidEntryPos >= 0)
6637  {
6638  TTrackElement MidTrackElement = Track->TrackElementAt(996, MidElement);
6639  int FirstRouteElementVecPos = MidTrackElement.Conn[MidEntryPos];
6640  int FirstRouteLinkPos = MidTrackElement.ConnLinkPos[MidEntryPos];
6641  int RouteNumber = -1;
6642  TAllRoutes::TRouteType RouteType = AllRoutes->GetRouteTypeAndNumber(34, FirstRouteElementVecPos, FirstRouteLinkPos, RouteNumber);
6643  if(RouteType == TAllRoutes::NotAutoSigsRoute)
6644  {
6645  TOneRoute &OR = AllRoutes->GetModifiableRouteAt(28, RouteNumber);
6646  int CorrectRouteID = OR.RouteID; //added at v2.13.0 as when last element removed & route removed from vector OR becomes the next route after the erased one and
6647  //elements can continue to be removed from that route
6648  TTrackElement TE = Track->TrackElementAt(997, FirstRouteElementVecPos);
6649 // if((TE.TrackType != SignalPost) && (TE.TrackType != Continuation)) //all autosigs routes have signalpost or continuation at 0 so they are automatically excluded
6650  { //above condition removed v2.17.0 so non-facing signal or continuation doesn't stop route being removed
6651  //if it is a facing signal then it will be detected below and not removed
6652  bool FirstPass = true; //added at v2.8.0
6653  while((OR.PrefDirSize() > 0) && (OR.RouteID == CorrectRouteID)) //remove the route up to but not including the next facing signal, in case a pref dir route extends to another signal
6654  { // && (OR.RouteID == RouteID) added at v2.13.0 to prevent another route having elements removed
6655  TPrefDirElement PDE = OR.GetFixedPrefDirElementAt(247, 0); //these will change at each element removal because OR is a reference to the real route
6656  int TVPos2 = PDE.GetTrackVectorPosition();
6657  if(FirstPass && (TVPos2 != FirstRouteElementVecPos)) //route is not directed away from cdt train, could be a call-on for another train (added at v2.8.0)
6658  {
6659  break;
6660  }
6661  TTrackElement TE2 = Track->TrackElementAt(998, TVPos2);
6663  {
6664  AllRoutes->RemoveRouteElement(22, TE2.HLoc, TE2.VLoc, PDE.GetELink());
6665  }
6666  else
6667  {
6668  break;
6669  }
6670  FirstPass = false;
6671  }
6672  AllRoutes->RebuildRailwayFlag = true;
6673  // to force ClearandRebuildRailway at next clock tick if not in zoom-out mode, to replot without stub route
6674  }
6675  }
6676  }
6677  Utilities->CallLogPop(1012);
6678 }
6679 
6680 // ---------------------------------------------------------------------------
6681 
6682 void TTrain::NewTrainService(int Caller, bool NoLogFlag) //, bool NoLogFlag added at v2.12.0 for new service tt skips
6683 // change to new train, give new service message
6684 //same RepeatNumber used for the new service
6685 { //Note that CumulativeDelayedRandMinsOneTrain carried forward from earlier train (parameter added at v2.13.0)
6686  TrainController->LogEvent("" + AnsiString(Caller) + ",NewTrainService" + "," + HeadCode);
6687  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",NewTrainService" + "," + HeadCode);
6688 
6689 /* restriction removed at v2.19.0
6690  if(PowerAtRail < 1)
6691  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can form new service when power restored)
6692  {
6693  if(!ZeroPowerNoNewServiceMessage)
6694  {
6695  TrainController->StopTTClockMessage(87, HeadCode + ": A train without power can't form a new service");
6696  }
6697  ZeroPowerNoNewServiceMessage = true;
6698  Utilities->CallLogPop(2142);
6699  return;
6700  }
6701 */
6703 
6704  AnsiString OriginalDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
6705 
6706  if(!NoLogFlag)
6707  {
6709  }
6710  UnplotTrain(3);
6713  StartSpeed = 0;
6718  HeadCode = NewHeadCode;
6720  if(!TrainDataEntryPtr->ExplicitDescription) //new at v2.15.0 see above
6721  {
6722  Description = OriginalDescription; //changed at v2.16.1 to train description
6723  }
6724  StoppedAtLocation = true;
6725  PlotStartPosition(5);
6727  // pale green
6730  TerminatedMessageSent = false;
6731  Utilities->CallLogPop(1022);
6732 }
6733 
6734 // ---------------------------------------------------------------------------
6735 
6736 void TTrain::RemainHere(int Caller) //added warnings & reminders at v2.19.0 (not sent from LogAction as not called when train terminates)
6737 {
6738  Utilities->CallLog.push_back(Utilities->TimeStamp() + AnsiString(Caller) + ",RemainHere" + "," + HeadCode);
6739  if(RemainHereLogNotSent) // to prevent repeated logs
6740  {
6741  TrainController->LogEvent(Utilities->TimeStamp() + AnsiString(Caller) + ",RemainHere" + "," + HeadCode);
6742  RemainHereLogNotSent = false;
6743  }
6745  {
6746  TDateTime ActualTime = TrainController->TTClockTime;
6747  AnsiString BaseLog = "", Location = ActionVectorEntryPtr->LocationName;
6748  AnsiString PerfLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + " terminated at " + Location;
6750  {
6751  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " WARNING: " + HeadCode + " terminated at " + Location;
6752  Display->WarningLog(25, PerfLog);
6753  PerfLogForm->PerformanceLog(65, BaseLog);
6754  }
6755  else if(ActionVectorEntryPtr->Reminder > 0)
6756  {
6757  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " REMINDER: " + HeadCode + " terminated at " + Location;
6758  Display->WarningLog(26, PerfLog);
6759  PerfLogForm->PerformanceLog(66, BaseLog);
6760  }
6761  else
6762  {
6763  PerfLogForm->PerformanceLog(67, PerfLog);
6764  }
6766  TerminatedMessageSent = true;
6767  }
6768  TimetableFinished = true;
6769  Utilities->CallLogPop(1023);
6770 }
6771 
6772 // ---------------------------------------------------------------------------
6773 
6774 void TTrain::SendMissedActionLogs(int Caller, int IncNum, TActionVectorEntry *Ptr)
6775 /*
6776  Enter with pointer at next expected action, and IncNum the number by which have to increase the pointer
6777  to reach the action that is valid for the train's current position. i.e. IncNum error messages to be sent
6778  except where an action is a departure, starting at the current value for the pointer
6779  If IncNum is -1, then send messages for all remaining actions, including Fer if present
6780  If IncNum is -2, then send messages for all remaining actions, except Fer if present
6781 */{
6782  if((Ptr->Command == "Snt") && Ptr->SignallerControl)
6783  {
6784  return; // if remove train that starts under signaller control no messages needed
6785 
6786  }
6787  TrainController->LogEvent("" + AnsiString(Caller) + ",SendMissedActionLogs," + AnsiString(IncNum) + "," + HeadCode);
6788  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SendMissedActionLogs," + AnsiString(IncNum) + "," + HeadCode);
6789  if(IncNum > 0)
6790  {
6791  for(int x = 0; x < IncNum; x++)
6792  {
6793  if(x > 0)
6794  {
6795  Ptr++;
6796  }
6797  // arrival - no need to test for termination as this section only covers missed actions up to the
6798  // arrival point - may terminate later but that not missed
6799  if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime > TDateTime(-1)))
6800  {
6802  }
6803  // arrival & departure
6804  if(Ptr->FormatType == TimeTimeLoc)
6805  {
6807  }
6808  // departure
6809  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
6810  {
6811  continue; // skip TimeLoc departures, message given for arrivals
6812  }
6813  // pass
6814  else if(Ptr->FormatType == PassTime)
6815  {
6817  }
6818  // split
6819  else if((Ptr->Command == "fsp") || (Ptr->Command == "rsp"))
6820  {
6822  }
6823  // jbo
6824  else if(Ptr->Command == "jbo")
6825  {
6827  }
6828  // dsc
6829  else if(Ptr->Command == "dsc") //new at v2.15.0
6830  {
6832  }
6833  //cms
6834  else if(Ptr->Command == "cms") //new at v2.21.0
6835  {
6837  }
6838  // Errors - have reached a station stop point (before a cdt) during Train->Update() so intervening actions can't
6839  // be starts, finishes or cdt
6840  else if((Ptr->Command == "Fns") || (Ptr->Command == "Frh") || (Ptr->Command == "Fer") || (Ptr->Command == "Fjo") || (Ptr->Command == "Snt") ||
6841  (Ptr->Command == "Sfs") || (Ptr->Command == "Snt-sh") || (Ptr->Command == "Sns") || (Ptr->Command == "Sns-sh") || (Ptr->Command == "Sns-fsh") ||
6842  (Ptr->Command == "cdt") || (Ptr->Command == "Frh-sh") || (Ptr->Command == "Fns-sh") || (Ptr->Command == "F-nshs") ||
6843  (Ptr->FormatType == Repeat))
6844  {
6845  throw Exception("Error - illegal command in SendMissedActionLogs for IncNum = " + AnsiString(IncNum) + ", and command = " + Ptr->Command);
6846  }
6847  }
6848  }
6849  else
6850  {
6851  bool IncludeFER = false;
6852  if(IncNum == -1)
6853  {
6854  IncludeFER = true;
6855  }
6856  while(true) // finish commands & repeats break out of loop
6857  {
6858  // Fer & excluded - send normal exit log to give minutes late or early - no, have already sent an unexpected exit message
6859  if(!IncludeFER && (Ptr->Command == "Fer"))
6860  {
6861  break;
6862  }
6863  // Fer & included
6864  else if(IncludeFER && (Ptr->Command == "Fer"))
6865  {
6867  break;
6868  }
6869  // Repeat
6870  else if(Ptr->FormatType == Repeat)
6871  {
6872  break;
6873  }
6874  // Fjo
6875  else if(Ptr->Command == "Fjo")
6876  {
6878  break;
6879  }
6880  // Frh
6881  else if(Ptr->Command == "Frh")
6882  {
6884  {
6886  TerminatedMessageSent = true;
6887  }
6888  break;
6889  }
6890  // Frh-sh
6891  else if(Ptr->Command == "Frh-sh")
6892  {
6894  {
6896  TerminatedMessageSent = true;
6897  }
6898  break;
6899  }
6900  // Fns, F-nshs, Fns-sh
6901  else if((Ptr->Command == "Fns") || (Ptr->Command == "F-nshs") || (Ptr->Command == "Fns-sh"))
6902  {
6904  break;
6905  }
6906  // end of breakout actions
6907  // arrival
6908  if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime > TDateTime(-1)))
6909  {
6910  if(IsTrainTerminating(1))
6911  {
6913  TerminatedMessageSent = true;
6914  }
6915  else
6916  {
6918  }
6919  }
6920  // arrival & departure
6921  else if(Ptr->FormatType == TimeTimeLoc)
6922  {
6924  }
6925  // departure
6926  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
6927  {
6928  Ptr++;
6929  continue; // skip TimeLoc departures, message given for arrivals
6930  }
6931  // pass
6932  else if(Ptr->FormatType == PassTime)
6933  {
6935  }
6936  // split
6937  else if((Ptr->Command == "fsp") || (Ptr->Command == "rsp"))
6938  {
6940  }
6941  // jbo
6942  else if(Ptr->Command == "jbo")
6943  {
6945  }
6946  // dsc
6947  else if(Ptr->Command == "dsc") //new at v2.15.0
6948  {
6949 // TrainController->LogActionError(65, HeadCode, "", FailMissedDSC, Ptr->LocationName); don't count as a missed event
6950  }
6951  // cms
6952  else if(Ptr->Command == "cms") //new at v2.21.0
6953  {
6954 // TrainController->LogActionError(, HeadCode, "", FailMissedCMS, Ptr->LocationName); don't count as a missed event
6955  }
6956  // cdt
6957  else if(Ptr->Command == "cdt")
6958  {
6959 // TrainController->LogActionError(25, HeadCode, "", FailMissedChangeDirection, Ptr->LocationName); //commented out at v2.12.0 as cdts not counted as missed events
6960  }
6961  // Errors
6962  else if((Ptr->Command == "Snt-sh") || (Ptr->Command == "Sfs") || (Ptr->Command == "Sns") || (Ptr->Command == "Sns-sh") ||
6963  (Ptr->Command == "Sns-fsh") || ((Ptr->Command == "Snt") && !Ptr->SignallerControl))
6964  {
6965  throw Exception("Error - illegal command in SendMissedActionLogs for IncNum = " + AnsiString(IncNum) + ", and command = " + Ptr->Command);
6966  }
6967  Ptr++;
6968  }
6969  TimetableFinished = true;
6970  }
6971  Utilities->CallLogPop(1021);
6972 }
6973 
6974 // ---------------------------------------------------------------------------
6975 
6976 bool TTrain::TrainToJoinIsAdjacent(int Caller, TTrain* &TrainToJoin)
6977 // ensure same repeatnumber
6978 {
6979  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainToJoinIsAdjacent" + "," + HeadCode);
6981 
6982  if(TrainToJoinTDEntry->TrainOperatingDataVector.at(RepeatNumber).RunningEntry != Running)
6983  {
6984  Utilities->CallLogPop(1024);
6985  return(false);
6986  }
6987  TrainToJoin = &(TrainController->TrainVectorAtIdent(33, TrainToJoinTDEntry->TrainOperatingDataVector.at(RepeatNumber).TrainID));
6988  if(TrainToJoin->StoppedAtLocation && (TrainToJoin->TrainMode == Timetable) && (TrainToJoin->ActionVectorEntryPtr->Command == "jbo"))
6989  {
6990  if((Track->TrackElementAt(610, LeadElement).Conn[LeadExitPos] == TrainToJoin->LeadElement) || (Track->TrackElementAt(611,
6991  LeadElement).Conn[LeadExitPos] == TrainToJoin->MidElement) || (Track->TrackElementAt(612, MidElement).Conn[MidEntryPos] == TrainToJoin->LeadElement)
6992  || (Track->TrackElementAt(613, MidElement).Conn[MidEntryPos] == TrainToJoin->MidElement))
6993  {
6994  Utilities->CallLogPop(1025);
6995  return(true);
6996  }
6997  }
6998  Utilities->CallLogPop(1026);
6999  return(false);
7000 }
7001 
7002 // ---------------------------------------------------------------------------
7003 
7004 bool TTrain::TrainToBeJoinedByIsAdjacent(int Caller, TTrain* &TrainToBeJoinedBy)
7005 // ensure same repeatnumber
7006 {
7007  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainToBeJoinedByIsAdjacent" + "," + HeadCode);
7008  TTrainDataEntry *TrainToBeJoinedByTDEntry = ActionVectorEntryPtr->LinkedTrainEntryPtr;
7009 
7010  if(TrainToBeJoinedByTDEntry->TrainOperatingDataVector.at(RepeatNumber).RunningEntry != Running)
7011  {
7012  Utilities->CallLogPop(1027);
7013  return(false);
7014  }
7015  TrainToBeJoinedBy = &(TrainController->TrainVectorAtIdent(15, TrainToBeJoinedByTDEntry->TrainOperatingDataVector.at(RepeatNumber).TrainID));
7016  if(TrainToBeJoinedBy->StoppedAtLocation && (TrainToBeJoinedBy->TrainMode == Timetable) && (TrainToBeJoinedBy->ActionVectorEntryPtr->Command == "Fjo"))
7017  {
7018  if((Track->TrackElementAt(614, LeadElement).Conn[LeadExitPos] == TrainToBeJoinedBy->LeadElement) || (Track->TrackElementAt(615,
7019  LeadElement).Conn[LeadExitPos] == TrainToBeJoinedBy->MidElement) || (Track->TrackElementAt(616,
7020  MidElement).Conn[MidEntryPos] == TrainToBeJoinedBy->LeadElement) || (Track->TrackElementAt(617,
7021  MidElement).Conn[MidEntryPos] == TrainToBeJoinedBy->MidElement))
7022  {
7023  Utilities->CallLogPop(1028);
7024  return(true);
7025  }
7026  }
7027  Utilities->CallLogPop(1029);
7028  return(false);
7029 }
7030 
7031 // ---------------------------------------------------------------------------
7032 
7033 void TTrain::NewShuttleFromNonRepeatService(int Caller, bool NoLogFlag) //bool NoLogFlag added at v2.12.0 for new service TT skips)
7034 { //same RepeatNumber (i.e. 0) used for the new shuttle
7035 //Note that CumulativeDelayedRandMinsOneTrain carried forward from earlier train (parameter added at v2.13.0)
7036  TrainController->LogEvent("" + AnsiString(Caller) + ",NewShuttleFromNonRepeatService" + "," + HeadCode);
7037  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",NewShuttleFromNonRepeatService" + "," + HeadCode);
7038 
7039 /* restriction removed at v2.19.0
7040  if(PowerAtRail < 1)
7041  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can for new service when power restored)
7042  {
7043  if(!ZeroPowerNoNewShuttleFromNonRepeatMessage)
7044  {
7045  TrainController->StopTTClockMessage(88, HeadCode + ": A train without power can't form a new service");
7046  }
7047  ZeroPowerNoNewShuttleFromNonRepeatMessage = true;
7048  Utilities->CallLogPop(2143);
7049  return;
7050  }
7051 */
7052  AnsiString NewHeadCode = ActionVectorEntryPtr->NonRepeatingShuttleLinkHeadCode;
7053  AnsiString OriginalDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
7054 
7055  if(!NoLogFlag)
7056  {
7058  }
7059  UnplotTrain(4);
7062  StartSpeed = 0;
7067  HeadCode = NewHeadCode;
7069  if(!TrainDataEntryPtr->ExplicitDescription) //new at v2.15.0 see above
7070  {
7071  Description = OriginalDescription; //changed at v2.16.1 to train description
7072  }
7073  IncrementalMinutes = TrainDataEntryPtr->ActionVector.back().RearStartOrRepeatMins;
7074  IncrementalDigits = TrainDataEntryPtr->ActionVector.back().FrontStartOrRepeatDigits;
7075  StoppedAtLocation = true;
7076  PlotStartPosition(6);
7078  // pale green
7081  TerminatedMessageSent = false;
7082  Utilities->CallLogPop(1078);
7083 }
7084 
7085 // ---------------------------------------------------------------------------
7086 
7087 void TTrain::RepeatShuttleOrRemainHere(int Caller, bool NoLogFlag) //bool NoLogFlag added at v2.12.0 for new service TT skips)
7088 // need to check whether all repeats finished or not
7089 { //Note that CumulativeDelayedRandMinsOneTrain carried forward from earlier train (parameter added at v2.13.0)
7090  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RepeatShuttleOrRemainHere" + "," + HeadCode);
7091  if(RemainHereLogNotSent) // to prevent repeated logs
7092  {
7093  TrainController->LogEvent("" + AnsiString(Caller) + ",RepeatShuttleOrRemainHere" + "," + HeadCode);
7094  RemainHereLogNotSent = false;
7095  }
7097  // finished all repeats
7098  {
7100  { //no need to suppress this LogAction because BecomeNewService won't be available in this case
7103  TerminatedMessageSent = true;
7104  // no need to clear message as no more actions
7105  }
7106  TimetableFinished = true;
7107  Utilities->CallLogPop(1080);
7108  return;
7109  }
7110 
7111 /* restriction removed at v2.19.0
7112  if(PowerAtRail < 1)
7113  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can for new service when power restored)
7114  {
7115  if(!ZeroPowerNoRepeatShuttleMessage)
7116  {
7117  TrainController->StopTTClockMessage(89, HeadCode + ": A train without power can't form a new service");
7118  }
7119  ZeroPowerNoRepeatShuttleMessage = true;
7120  Utilities->CallLogPop(2144);
7121  return;
7122  }
7123 */
7124  int TempRepeatNumber = RepeatNumber + 1;
7125  // need the next repeat value in order to obtain a correct NewHeadCode, but don't increase it
7126  // until after LogAction or the wrong time will be used
7127  AnsiString NewHeadCode = TrainController->GetRepeatHeadCode(6, ActionVectorEntryPtr->OtherHeadCode, TempRepeatNumber, IncrementalDigits);
7128  AnsiString OriginalDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
7129 
7130  if(!NoLogFlag)
7131  {
7133  }
7134  RepeatNumber++;
7135  UnplotTrain(5);
7138  StartSpeed = 0;
7143  HeadCode = NewHeadCode;
7145  if(!TrainDataEntryPtr->ExplicitDescription) //new at v2.15.0 see above
7146  {
7147  Description = OriginalDescription; //changed at v2.16.1 to train description
7148  }
7149  StoppedAtLocation = true;
7150  PlotStartPosition(7);
7152  // pale green
7155  TerminatedMessageSent = false;
7156  Utilities->CallLogPop(1079);
7157 }
7158 
7159 // ---------------------------------------------------------------------------
7160 
7161 void TTrain::RepeatShuttleOrNewNonRepeatService(int Caller, bool NoLogFlag) //bool NoLogFlag added at v2.12.0 for new service TT skips
7162 { //Note that CumulativeDelayedRandMinsOneTrain carried forward from earlier train (parameter added at v2.13.0)
7163  TrainController->LogEvent("" + AnsiString(Caller) + ",RepeatShuttleOrNewNonRepeatService" + "," + HeadCode);
7164  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RepeatShuttleOrNewNonRepeatService" + "," + HeadCode);
7165 
7166 /* restriction removed at v2.19.0
7167  if(PowerAtRail < 1)
7168  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can for new service when power restored)
7169  {
7170  if(!ZeroPowerNoRepeatShuttleOrNewServiceMessage)
7171  {
7172  TrainController->StopTTClockMessage(90, HeadCode + ": A train without power can't form a new service");
7173  }
7174  ZeroPowerNoRepeatShuttleOrNewServiceMessage = true;
7175  Utilities->CallLogPop(2145);
7176  return;
7177  }
7178 */
7180  // finished all repeats
7181  {
7182  AnsiString NewHeadCode = ActionVectorEntryPtr->NonRepeatingShuttleLinkHeadCode;
7183  if(!NoLogFlag)
7184  {
7186  }
7187  RepeatNumber = 0;
7188  AnsiString OriginalDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
7189  UnplotTrain(6);
7192  StartSpeed = 0;
7194  TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).TrainID = TrainID; // but note that RepeatNumber = 0
7195  TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).RunningEntry = Running; // but note that RepeatNumber = 0
7197  HeadCode = NewHeadCode;
7199  if(!TrainDataEntryPtr->ExplicitDescription) //new at v2.15.0 see above
7200  {
7201  Description = OriginalDescription; //changed at v2.16.1 to train description
7202  }
7203  StoppedAtLocation = true;
7204  PlotStartPosition(9);
7208  TerminatedMessageSent = false;
7209  Utilities->CallLogPop(1081);
7210  return;
7211  }
7212  int TempRepeatNumber = RepeatNumber + 1;
7213  // need the next repeat value in order to obtain a correct NewHeadCode, but don't increase it
7214  // until after LogAction or the wrong time will be used
7215  AnsiString NewHeadCode = TrainController->GetRepeatHeadCode(7, ActionVectorEntryPtr->OtherHeadCode, TempRepeatNumber, IncrementalDigits);
7216  AnsiString OriginalDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
7217 
7218  if(!NoLogFlag)
7219  {
7221  }
7222  RepeatNumber++;
7223  UnplotTrain(7);
7226  StartSpeed = 0;
7231  HeadCode = NewHeadCode;
7233  if(!TrainDataEntryPtr->ExplicitDescription) //new at v2.15.0 see above
7234  {
7235  Description = OriginalDescription; //changed at v2.16.1 to train description
7236  }
7237  StoppedAtLocation = true;
7238  PlotStartPosition(8);
7240  // pale green
7243  TerminatedMessageSent = false;
7244  Utilities->CallLogPop(1082);
7245 }
7246 
7247 // ---------------------------------------------------------------------------
7248 
7250 {
7251  // Search ActionVector from the position after the entry value for Ptr to the end, and return true if find a Finish
7252  // entry before Fer or TimeLoc. No point checking for TimeTimeLoc since at a stop location now so a later TimeTimeLoc
7253  // must be preceded by a TimeLoc departure
7254  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsTrainTerminating" + "," + HeadCode);
7255  for(unsigned int x = 1; x < TrainDataEntryPtr->ActionVector.size(); x++)
7256  {
7258  {
7259  if(((ActionVectorEntryPtr + x)->Command == "Fer") || ((ActionVectorEntryPtr + x)->FormatType == TimeLoc))
7260  {
7261  Utilities->CallLogPop(1083);
7262  return(false);
7263  }
7264  else if((ActionVectorEntryPtr + x)->SequenceType == FinishSequence)
7265  {
7266  Utilities->CallLogPop(1084);
7267  return(true);
7268  }
7269  }
7270  }
7271  Utilities->CallLogPop(1085);
7272  return(false);
7273 }
7274 
7275 // ---------------------------------------------------------------------------
7276 
7277 bool TTrain::AbleToMove(int Caller)
7278 {
7279  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",AbleToMove" + "," + HeadCode);
7280  bool Able = true;
7281 
7282  if(Crashed || Derailed || StoppedAtBuffers || StoppedAtSignal || StoppedWithoutPower) // StoppedWithoutPower added at v2.4.0
7283  {
7284  // StoppedForTrainInFront removed as tested below
7285  Utilities->CallLogPop(2146); // added v2.4.0
7286  return(false); // added v2.4.0
7287  }
7288  if(LeadElement > -1)
7289  {
7290  if(Track->TrackElementAt(801, LeadElement).TrackType == Buffers) //moved up here from 'else' below at v2.12.0
7291  {
7292  StoppedForTrainInFront = false;
7293  TrainInFront = false;
7294  // don't set StoppedAtBuffers as (presumably) StoppedAtLocation & leave it at that
7295  Utilities->CallLogPop(2456);
7296  return(false);
7297  }
7298  int FrontPos = Track->TrackElementAt(678, LeadElement).Conn[LeadExitPos];
7299  int FrontEntryPos = Track->TrackElementAt(679, LeadElement).ConnLinkPos[LeadExitPos];
7300  if((FrontPos > -1) && (TrainMode == Signaller) && TrainInFront) //check if train in front still there
7301  {
7302  TTrackElement TrackElement = Track->TrackElementAt(680, FrontPos);
7303  if((TrackElement.TrackType != Bridge) && (TrackElement.TrainIDOnElement == -1))
7304  {
7305  Able = true;
7306  TrainInFront = false;
7307  }
7308  else if((TrackElement.TrackType == Bridge) && (FrontEntryPos < 2) && (TrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit01 == -1))
7309  {
7310  Able = true;
7311  TrainInFront = false;
7312  }
7313  else if((TrackElement.TrackType == Bridge) && (FrontEntryPos > 1) && (TrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit23 == -1))
7314  {
7315  Able = true;
7316  TrainInFront = false;
7317  }
7318  else
7319  {
7320  Able = false; //added at v2.12.0 as train still in front so don't want signaller popup options to move available
7321  }
7322  }
7323  }
7324  else // leaving at a continuation so keep going
7325  {
7326  Able = true;
7327  StoppedForTrainInFront = false;
7328  TrainInFront = false;
7329  }
7330  Utilities->CallLogPop(1454);
7331  return(Able);
7332 }
7333 
7334 // ---------------------------------------------------------------------------
7335 
7337 {
7338  // first check if a train immediately in front (may have moved there since this train stopped so StoppedForTrainInFront
7339  // won't be set; if there is a train then set StoppedForTrainInFront
7340  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",AbleToMoveButForSignalOrTrainInFront" + "," + HeadCode);
7341  // addition below for v1.3.2 after Carwyn Thomas fault reported 24/05/15 - need to check if exiting at continuation (LeadElement == -1) as if so fails at VecPos = .....
7342  if(LeadElement == -1) // exiting at continuation
7343  {
7344  Utilities->CallLogPop(2045);
7345  return(false);
7346  }
7347  // end of addition
7348  int VecPos = Track->TrackElementAt(654, LeadElement).Conn[LeadExitPos];
7349  int NextEntryPos = Track->TrackElementAt(655, LeadElement).ConnLinkPos[LeadExitPos];
7350 
7351  if(Track->OtherTrainOnTrack(5, VecPos, NextEntryPos, TrainID))
7352  {
7353  TrainInFront = true;
7354  Utilities->CallLogPop(1455);
7355  return(false);
7356  }
7357  else
7358  {
7359  Utilities->CallLogPop(1456);
7361  // StoppedWithoutPower added v2.4.0
7362  }
7363 }
7364 
7365 // ---------------------------------------------------------------------------
7366 
7368 {
7369  // unplots & replots train, which checks for facing signal and sets StoppedAtSignal if req'd
7370  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SignallerChangeTrainDirection" + "," + HeadCode);
7371  TColor TempColour = BackgroundColour;
7372 
7373  UnplotTrain(8);
7376  StartSpeed = 0;
7377  PlotStartPosition(2);
7378  PlotTrainWithNewBackgroundColour(26, TempColour, Display);
7379 
7380  //now erase a stub route if there is one, added at v2.5.1
7381  //first element of route is now immediately behind the train (i.e. next to MidElement)
7382  if(MidEntryPos >= 0)
7383  {
7384  TTrackElement MidTrackElement = Track->TrackElementAt(1000, MidElement);
7385  int FirstRouteElementVecPos = MidTrackElement.Conn[MidEntryPos];
7386  int FirstRouteLinkPos = MidTrackElement.ConnLinkPos[MidEntryPos];
7387  int RouteNumber = -1;
7388  TAllRoutes::TRouteType RouteType = AllRoutes->GetRouteTypeAndNumber(35, FirstRouteElementVecPos, FirstRouteLinkPos, RouteNumber);
7389  if(RouteType == TAllRoutes::NotAutoSigsRoute)
7390  {
7391  TOneRoute &OR = AllRoutes->GetModifiableRouteAt(29, RouteNumber);
7392  int CorrectRouteID = OR.RouteID; //added at v2.13.0 as when last element removed & route removed from vector OR becomes the next route after the erased one and
7393  //elements can continue to be removed from that route
7394  TTrackElement TE = Track->TrackElementAt(1001, FirstRouteElementVecPos);
7395 // if((TE.TrackType != SignalPost) && (TE.TrackType != Continuation)) //all autosigs routes have signalpost or continuation at 0 so they are automatically excluded
7396  { //above condition removed at v2.17.0 so non-facing signal or continuation doesn't stop route being removed
7397  //if it is a facing signal then it will be detected below and not removed
7398  bool FirstPass = true; //added at v2.8.0
7399  while((OR.PrefDirSize() > 0) && (OR.RouteID == CorrectRouteID)) //remove the route up to but not including the next facing signal, in case a pref dir route extends to another signal
7400  { // && (OR.RouteID == RouteID) added at v2.13.0 to prevent another route having elements removed
7401  TPrefDirElement PDE = OR.GetFixedPrefDirElementAt(248, 0); //these will change at each element removal because OR is a reference to the real route
7402  int TVPos2 = PDE.GetTrackVectorPosition();
7403  if(FirstPass && (TVPos2 != FirstRouteElementVecPos)) //route is not directed away from cdt train, could be a call-on for another train (added at v2.8.0)
7404  {
7405  break;
7406  }
7407  TTrackElement TE2 = Track->TrackElementAt(1002, TVPos2);
7409  {
7410  AllRoutes->RemoveRouteElement(23, TE2.HLoc, TE2.VLoc, PDE.GetELink());
7411  }
7412  else
7413  {
7414  break;
7415  }
7416  FirstPass = false;
7417  }
7418  AllRoutes->RebuildRailwayFlag = true;
7419  // to force ClearandRebuildRailway at next clock tick if not in zoom-out mode, to replot without stub route
7420  }
7421  }
7422  }
7423  Utilities->CallLogPop(1102);
7424 }
7425 
7426 // ---------------------------------------------------------------------------
7427 
7429 {
7430  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString(Ptr - &TrainDataEntryPtr->ActionVector.front()) +
7431  ",FloatingLabelNextString" + "," + HeadCode);
7432  AnsiString RetStr = "", LocationName = "";
7433  //record action time - may be arrival, departure or event for use later (added at v2.13.2)
7434  TDateTime ActionTime = Ptr->ArrivalTime;
7435  if(ActionTime == TDateTime(-1))
7436  {
7437  ActionTime = Ptr->DepartureTime;
7438  }
7439  if(ActionTime == TDateTime(-1))
7440  {
7441  ActionTime = Ptr->EventTime;
7442  }
7443  //If ActionTime still TDateTime(-1) then the train has terminated and 'None...' will be returned
7444  //Now correct it for repeats
7445  if(ActionTime != TDateTime(-1))
7446  {
7447  ActionTime = GetTrainTime(64, ActionTime);
7448  }
7449  if(int(DelayedRandMins) > 0)
7450  {
7451  if((Ptr->Command != "") && (Ptr->Command[1] == 'S'))
7452  {
7453  throw Exception("Error - start entry in FloatingLabelNextString");
7454  }
7455  if(Ptr->FormatType == TimeTimeLoc)
7456  {
7457  if(TrainMode == Timetable)
7458  {
7459  if(!TrainAtLocation(0, LocationName) || (LocationName != Ptr->LocationName))
7460  // not arrived yet in tt mode
7461  {
7462  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(2, Ptr->ArrivalTime + TDateTime(DelayedRandMins/1440)));
7463  }
7464  else
7465  {
7466  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(3, Ptr->DepartureTime));
7467  }
7468  }
7469  else // TrainMode == Signaller
7470  {
7471  if(!DepartureTimeSet) // not arrived yet
7472  {
7473  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(45, Ptr->ArrivalTime + TDateTime(DelayedRandMins/1440)));
7474  }
7475  else
7476  {
7477  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(36, Ptr->DepartureTime));
7478  }
7479  }
7480  }
7481  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
7482  {
7483  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(4, Ptr->ArrivalTime + TDateTime(DelayedRandMins/1440)));
7484  }
7485  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
7486  {
7487  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(5, Ptr->DepartureTime));
7488  }
7489  else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture) //added at v2.12.0 for becoming new service early (see BecomeNewservice)
7490  {
7491  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(46, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7492  }
7493  else if(Ptr->FormatType == PassTime) //must come after 'else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture)'
7494  {
7495  RetStr = "Pass " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(31, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7496  }
7497  else if(Ptr->Command == "Fns")
7498  {
7499  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(8, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " +
7500  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(6, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7501  RetStr = GetNewServiceDepartureInfo(0, Ptr, RepeatNumber, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7502  }
7503  else if(Ptr->Command == "F-nshs")
7504  {
7505  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode + " at " + Ptr->LocationName + " at approx. " +
7506  Utilities->Format96HHMM(GetTrainTime(32, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7507  RetStr = GetNewServiceDepartureInfo(1, Ptr, 0, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7508  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
7509  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
7510  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
7511  }
7512  else if((Ptr->Command == "Fns-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7513  {
7514  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(9, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7515  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(7, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7516  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7517  RetStr = GetNewServiceDepartureInfo(2, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7518  }
7519  else if((Ptr->Command == "Fns-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7520  {
7521  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode,
7522  +" at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(8, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7523  RetStr = GetNewServiceDepartureInfo(3, Ptr, 0, Ptr->NonRepeatingShuttleLinkEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7524  }
7525  else if((Ptr->Command == "Frh-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7526  {
7527  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(10, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7528  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(9, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7529  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7530  RetStr = GetNewServiceDepartureInfo(4, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7531  }
7532  else if((Ptr->Command == "Frh-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7533  {
7534  RetStr ="None, train terminated at " + Ptr->LocationName;
7535  }
7536  else if(Ptr->Command == "Frh")
7537  {
7538  RetStr = "None, train terminated at " + Ptr->LocationName;
7539  }
7540  else if(Ptr->Command == "Fer")
7541  {
7542  AnsiString AllowedExits = "";
7543  RetStr = "Exit railway" + TrainController->GetExitLocationAndAt(1, Ptr->ExitList, AllowedExits) + " at approx. " + Utilities->Format96HHMM(GetTrainTime(10, Ptr->EventTime + TDateTime(DelayedRandMins/1440))) + AllowedExits;
7544  }
7545  else if(Ptr->Command == "Fjo")
7546  {
7547  RetStr = "Join " + TrainController->GetRepeatHeadCode(11, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName + " at approx. " +
7548  Utilities->Format96HHMM(GetTrainTime(11, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7549  }
7550  else if(Ptr->Command == "jbo")
7551  {
7552  RetStr = "Joined by " + TrainController->GetRepeatHeadCode(12, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7553  " at approx. " + Utilities->Format96HHMM(GetTrainTime(12, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7554  }
7555  else if(Ptr->Command == "fsp")
7556  {
7557  RetStr = "Front split to " + TrainController->GetRepeatHeadCode(13, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7558  " at approx. " + Utilities->Format96HHMM(GetTrainTime(13, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7559  }
7560  else if(Ptr->Command == "rsp")
7561  {
7562  RetStr = "Rear split to " + TrainController->GetRepeatHeadCode(14, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7563  " at approx. " + Utilities->Format96HHMM(GetTrainTime(14, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7564  }
7565  else if(Ptr->Command == "cdt")
7566  {
7567  RetStr = "Change direction at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(15, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7568  }
7569  else if(Ptr->Command == "dsc")
7570  {
7571  RetStr = "Change description at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(65, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7572  }
7573  else if(Ptr->Command == "cms")
7574  {
7575  RetStr = "Change maximum speed to " + Ptr->NewMaxSpeed + " at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(75, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7576  }
7577  }
7578  else if(TrainController->TTClockTime > ActionTime) //condition added at v2.13.2 for trains that are delayed other than suffering a random delay
7579  {
7580  if((Ptr->Command != "") && (Ptr->Command[1] == 'S'))
7581  {
7582  throw Exception("Error - start entry in FloatingLabelNextString where TTClockTime > ActionTime");
7583  }
7584  if(Ptr->FormatType == TimeTimeLoc)
7585  {
7586  if(TrainMode == Timetable)
7587  {
7588  if(!TrainAtLocation(4, LocationName) || (LocationName != Ptr->LocationName))
7589  // not arrived yet in tt mode
7590  {
7591  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7592  }
7593  else
7594  {
7595  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);
7596  }
7597  }
7598  else // TrainMode == Signaller
7599  {
7600  if(!DepartureTimeSet) // not arrived yet
7601  {
7602  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7603  }
7604  else
7605  {
7606  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);
7607  }
7608  }
7609  }
7610  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
7611  {
7612  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7613  }
7614  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
7615  {
7616  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);
7617  }
7618  else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture) //added at v2.12.0 for becoming new service early (see BecomeNewservice)
7619  {
7620  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7621  }
7622  else if(Ptr->FormatType == PassTime) //must come after 'else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture)'
7623  {
7624  RetStr = "Pass " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7625  }
7626  else if(Ptr->Command == "Fns")
7627  {
7628  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(60, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " +
7629  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7630  RetStr = GetNewServiceDepartureInfo(19, Ptr, RepeatNumber, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7631  }
7632  else if(Ptr->Command == "F-nshs")
7633  {
7634  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode + " at " + Ptr->LocationName + " at approx. " +
7636  RetStr = GetNewServiceDepartureInfo(20, Ptr, 0, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7637  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
7638  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
7639  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
7640  }
7641  else if((Ptr->Command == "Fns-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7642  {
7643  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(61, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7644  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7645  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7646  RetStr = GetNewServiceDepartureInfo(21, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7647  }
7648  else if((Ptr->Command == "Fns-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7649  {
7650  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode,
7651  +" at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7652  RetStr = GetNewServiceDepartureInfo(22, Ptr, 0, Ptr->NonRepeatingShuttleLinkEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7653  }
7654  else if((Ptr->Command == "Frh-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7655  {
7656  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(62, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7657  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7658  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7659  RetStr = GetNewServiceDepartureInfo(23, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7660  }
7661  else if((Ptr->Command == "Frh-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7662  {
7663  RetStr ="None, train terminated at " + Ptr->LocationName;
7664  }
7665  else if(Ptr->Command == "Frh")
7666  {
7667  RetStr = "None, train terminated at " + Ptr->LocationName;
7668  }
7669  else if(Ptr->Command == "Fer")
7670  {
7671  AnsiString AllowedExits = "";
7672  RetStr = "Exit railway" + TrainController->GetExitLocationAndAt(5, Ptr->ExitList, AllowedExits) /*+ " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime)*/ + AllowedExits;
7673  }
7674  else if(Ptr->Command == "Fjo")
7675  {
7676  RetStr = "Join " + TrainController->GetRepeatHeadCode(63, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;// + " at approx. " +
7677 // Utilities->Format96HHMM(TrainController->TTClockTime);
7678  }
7679  else if(Ptr->Command == "jbo")
7680  {
7681  RetStr = "Joined by " + TrainController->GetRepeatHeadCode(64, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;// +
7682 // " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7683  }
7684  else if(Ptr->Command == "fsp")
7685  {
7686  RetStr = "Front split to " + TrainController->GetRepeatHeadCode(65, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7687  " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7688  }
7689  else if(Ptr->Command == "rsp")
7690  {
7691  RetStr = "Rear split to " + TrainController->GetRepeatHeadCode(66, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7692  " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7693  }
7694  else if(Ptr->Command == "cdt")
7695  {
7696  RetStr = "Change direction at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7697  }
7698  else if(Ptr->Command == "dsc")
7699  {
7700  RetStr = "Change description at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7701  }
7702  else if(Ptr->Command == "cms")
7703  {
7704  RetStr = "Change maximum speed to " + Ptr->NewMaxSpeed + " at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7705  }
7706  }
7707  else //train not delayed
7708  {
7709  if((Ptr->Command != "") && (Ptr->Command[1] == 'S'))
7710  {
7711  throw Exception("Error - start entry in FloatingLabelNextString in final 'else'");
7712  }
7713  if(Ptr->FormatType == TimeTimeLoc)
7714  {
7715  if(TrainMode == Timetable)
7716  {
7717  if(!TrainAtLocation(3, LocationName) || (LocationName != Ptr->LocationName))
7718  // not arrived yet in tt mode
7719  {
7720  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(48, Ptr->ArrivalTime));
7721  }
7722  else
7723  {
7724  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(3, Ptr->DepartureTime));
7725  }
7726  }
7727  else // TrainMode == Signaller
7728  {
7729  if(!DepartureTimeSet) // not arrived yet
7730  {
7731  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(49, Ptr->ArrivalTime));
7732  }
7733  else
7734  {
7735  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(36, Ptr->DepartureTime));
7736  }
7737  }
7738  }
7739  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
7740  {
7741  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(50, Ptr->ArrivalTime));
7742  }
7743  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
7744  {
7745  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(5, Ptr->DepartureTime));
7746  }
7747  else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture) //added at v2.12.0 for becoming new service early (see BecomeNewservice)
7748  {
7749  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(51, Ptr->EventTime));
7750  }
7751  else if(Ptr->FormatType == PassTime) //must come after 'else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture)'
7752  {
7753  RetStr = "Pass " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(52, Ptr->EventTime));
7754  }
7755  else if(Ptr->Command == "Fns")
7756  {
7757  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(53, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " +
7758  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(53, Ptr->EventTime));
7759  RetStr = GetNewServiceDepartureInfo(10, Ptr, RepeatNumber, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7760  }
7761  else if(Ptr->Command == "F-nshs")
7762  {
7763  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode + " at " + Ptr->LocationName + " at approx. " +
7765  RetStr = GetNewServiceDepartureInfo(12, Ptr, 0, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7766  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
7767  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
7768  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
7769  }
7770  else if((Ptr->Command == "Fns-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7771  {
7772  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(54, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7773  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(55, Ptr->EventTime));
7774  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7775  RetStr = GetNewServiceDepartureInfo(14, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7776  }
7777  else if((Ptr->Command == "Fns-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7778  {
7779  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode,
7780  +" at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(56, Ptr->EventTime));
7781  RetStr = GetNewServiceDepartureInfo(16, Ptr, 0, Ptr->NonRepeatingShuttleLinkEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7782  }
7783  else if((Ptr->Command == "Frh-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7784  {
7785  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(55, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7786  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(57, Ptr->EventTime));
7787  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7788  RetStr = GetNewServiceDepartureInfo(18, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7789  }
7790  else if((Ptr->Command == "Frh-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7791  {
7792  RetStr ="None, train terminated at " + Ptr->LocationName;
7793  }
7794  else if(Ptr->Command == "Frh")
7795  {
7796  RetStr = "None, train terminated at " + Ptr->LocationName;
7797  }
7798  else if(Ptr->Command == "Fer")
7799  {
7800  AnsiString AllowedExits = "";
7801  RetStr = "Exit railway" + TrainController->GetExitLocationAndAt(4, Ptr->ExitList, AllowedExits) /*+ " at approx. " + Utilities->Format96HHMM(GetTrainTime(62, Ptr->EventTime))*/ + AllowedExits;
7802  }
7803  else if(Ptr->Command == "Fjo")
7804  {
7805  RetStr = "Join " + TrainController->GetRepeatHeadCode(56, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;// + " at approx. " +
7806 // Utilities->Format96HHMM(GetTrainTime(58, Ptr->EventTime));
7807  }
7808  else if(Ptr->Command == "jbo")
7809  {
7810  RetStr = "Joined by " + TrainController->GetRepeatHeadCode(57, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;// +
7811 // " at approx. " + Utilities->Format96HHMM(GetTrainTime(59, Ptr->EventTime));
7812  }
7813  else if(Ptr->Command == "fsp")
7814  {
7815  RetStr = "Front split to " + TrainController->GetRepeatHeadCode(58, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7816  " at approx. " + Utilities->Format96HHMM(GetTrainTime(60, Ptr->EventTime));
7817  }
7818  else if(Ptr->Command == "rsp")
7819  {
7820  RetStr = "Rear split to " + TrainController->GetRepeatHeadCode(59, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7821  " at approx. " + Utilities->Format96HHMM(GetTrainTime(61, Ptr->EventTime));
7822  }
7823  else if(Ptr->Command == "cdt")
7824  {
7825  RetStr = "Change direction at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(63, Ptr->EventTime));
7826  }
7827  else if(Ptr->Command == "dsc")
7828  {
7829  RetStr = "Change description at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(66, Ptr->EventTime));
7830  }
7831  else if(Ptr->Command == "cms")
7832  {
7833  RetStr = "Change maximum speed to " + Ptr->NewMaxSpeed + " at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(76, Ptr->EventTime));
7834  }
7835  }
7836  Utilities->CallLogPop(1124);
7837  return(RetStr);
7838 }
7839 
7840 // ---------------------------------------------------------------------------
7841 /* as was
7842 AnsiString TTrain::FloatingLabelNextString(int Caller, TActionVectorEntry *Ptr)
7843 {
7844  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString(Ptr - &TrainDataEntryPtr->ActionVector.front()) +
7845  ",FloatingLabelNextString" + "," + HeadCode);
7846  AnsiString RetStr = "", LocationName = "";
7847 
7848  if((Ptr->Command != "") && (Ptr->Command[1] == 'S'))
7849  {
7850  throw Exception("Error - start entry in FloatingLabelNextString");
7851  }
7852  if(Ptr->FormatType == TimeTimeLoc)
7853  {
7854  if(TrainMode == Timetable)
7855  {
7856  if(!TrainAtLocation(, LocationName) || (LocationName != Ptr->LocationName))
7857  // not arrived yet in tt mode
7858  {
7859  RetStr = "Arrive " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->ArrivalTime));
7860  }
7861  else
7862  {
7863  RetStr = "Depart " + Ptr->LocationName + " approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(, Ptr->DepartureTime));
7864  }
7865  }
7866  else // TrainMode == Signaller
7867  {
7868  if(!DepartureTimeSet) // not arrived yet
7869  {
7870  RetStr = "Arrive " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->ArrivalTime));
7871  }
7872  else
7873  {
7874  RetStr = "Depart " + Ptr->LocationName + " approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(, Ptr->DepartureTime));
7875  }
7876  }
7877  }
7878  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
7879  {
7880  RetStr = "Arrive " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->ArrivalTime));
7881  }
7882  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
7883  {
7884  RetStr = "Depart " + Ptr->LocationName + " approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(, Ptr->DepartureTime));
7885  }
7886  else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture) //added at v2.12.0 for becoming new service early (see BecomeNewservice)
7887  {
7888  RetStr = "Depart " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7889  }
7890  else if(Ptr->FormatType == PassTime) //must come after 'else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture)'
7891  {
7892  RetStr = "Pass " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7893  }
7894  else if(Ptr->Command == "Fns")
7895  {
7896  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " +
7897  Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7898  RetStr = GetNewServiceDepartureInfo(, Ptr, RepeatNumber, Ptr->LinkedTrainEntryPtr, RetStr); //if there is a next service this adds the new service departure time to RetStr
7899  }
7900  else if(Ptr->Command == "F-nshs")
7901  {
7902  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode + " at " + Ptr->LocationName + " at " +
7903  Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7904  RetStr = GetNewServiceDepartureInfo(, Ptr, 0, Ptr->LinkedTrainEntryPtr, RetStr); //if there is a next service this adds the new service departure time to RetStr
7905  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
7906  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
7907  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
7908  }
7909  else if((Ptr->Command == "Fns-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7910  {
7911  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7912  Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7913  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7914  RetStr = GetNewServiceDepartureInfo(, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr); //if there is a next service this adds the new service departure time to RetStr
7915  }
7916  else if((Ptr->Command == "Fns-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7917  {
7918  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode,
7919  +" at " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7920  RetStr = GetNewServiceDepartureInfo(, Ptr, 0, Ptr->NonRepeatingShuttleLinkEntryPtr, RetStr); //if there is a next service this adds the new service departure time to RetStr
7921  }
7922  else if((Ptr->Command == "Frh-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7923  {
7924  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7925  Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7926  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7927  RetStr = GetNewServiceDepartureInfo(, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr); //if there is a next service this adds the new service departure time to RetStr
7928  }
7929  else if((Ptr->Command == "Frh-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7930  {
7931  RetStr ="None, train terminated at " + Ptr->LocationName;
7932  }
7933  else if(Ptr->Command == "Frh")
7934  {
7935  RetStr = "None, train terminated at " + Ptr->LocationName;
7936  }
7937  else if(Ptr->Command == "Fer")
7938  {
7939  AnsiString AllowedExits = "";
7940  RetStr = "Exit railway" + TrainController->GetExitLocationAndAt(, Ptr->ExitList, AllowedExits) + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime)) + AllowedExits;
7941  }
7942  else if(Ptr->Command == "Fjo")
7943  {
7944  RetStr = "Join " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName + " at " +
7945  Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7946  }
7947  else if(Ptr->Command == "jbo")
7948  {
7949  RetStr = "Joined by " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7950  " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7951  }
7952  else if(Ptr->Command == "fsp")
7953  {
7954  RetStr = "Front split to " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7955  " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7956  }
7957  else if(Ptr->Command == "rsp")
7958  {
7959  RetStr = "Rear split to " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7960  " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7961  }
7962  else if(Ptr->Command == "cdt")
7963  {
7964  RetStr = "Change direction at " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7965  }
7966  Utilities->CallLogPop();
7967  return(RetStr);
7968 }
7969 */
7970 // ---------------------------------------------------------------------------
7971 
7972 AnsiString TTrain::GetNewServiceDepartureInfo(int Caller, TActionVectorEntry *Ptr, int RptNum, TTrainDataEntry *LinkedTrainDataPtr, AnsiString RetStr, bool TimetableTime)
7973 { //last bool added at v2.13.2 so departure info adds random delay if actual rather than not timetable time required
7974  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString(Ptr - &TrainDataEntryPtr->ActionVector.front()) + ","
7975  + AnsiString(RptNum) + ",GetNewServiceDepartureInfo," + HeadCode);
7976  AnsiString DepTime = "", EventTime = "";
7977  bool CDTFlag = false; //reports if train changes direction before departs
7978  TActionVector NewServiceAV = LinkedTrainDataPtr->ActionVector;
7979  AnsiString CurrentLocation = NewServiceAV.at(0).LocationName; //added at v2.12.0 to show departure direction
7980  AnsiString TowardsLocation = ""; //added at v2.12.0 to show departure direction
7981  for(TActionVectorIterator AVI = NewServiceAV.begin(); AVI < NewServiceAV.end(); AVI++) //added at v2.12.0 to obtain departure direction
7982  {
7983  if((AVI->LocationName != CurrentLocation) && (AVI->LocationName != "") && (TowardsLocation == ""))
7984  {
7985  TowardsLocation = AVI->LocationName;
7986  }
7987  else if((AVI->Command == "Fer") && (TowardsLocation == "") && !AVI->ExitList.empty())
7988  {
7989  TTrackElement TE = Track->TrackElementAt(1452, (AVI->ExitList.front()));
7990  if(TE.ActiveTrackElementName != "")
7991  {
7992  TowardsLocation = TE.ActiveTrackElementName;
7993  }
7994  else
7995  {
7996  TowardsLocation = AnsiString("track element ID ") + TE.ElementID;
7997  }
7998  }
7999  }
8000 
8001  for(TActionVectorIterator AVI = NewServiceAV.begin(); AVI < NewServiceAV.end(); AVI++)
8002  {
8003  if(AVI->Command == "cdt")
8004  {
8005  CDTFlag = !CDTFlag; //toggles flag - allows for there being more than one cdt before departure
8006  continue;
8007  }
8008  if((AVI->Command == "fsp") || (AVI->Command == "rsp"))
8009  {
8010  TDateTime TTTime = TrainController->GetControllerTrainTime(19, AVI->EventTime, RptNum, IncrementalMinutes);
8011  if((DelayedRandMins >= 1) && !TimetableTime)
8012  {
8013  EventTime = Utilities->Format96HHMM(TTTime + TDateTime(DelayedRandMins/1440));
8014  }
8015  else if((TrainController->TTClockTime > TTTime) && !TimetableTime)
8016  {
8018  }
8019  else //((DelayedRandMins == 0) && (TTClockTime <= TTTime)) || TimetableTime
8020  {
8021  EventTime = Utilities->Format96HHMM(TTTime);
8022  }
8023  RetStr += "\nNew service splits at approx. " + EventTime;
8024  Utilities->CallLogPop(2234);
8025  return(RetStr);
8026  }
8027  if(AVI->Command == "jbo") //added at v2.15.0
8028  {
8029  TDateTime TTTime = TrainController->GetControllerTrainTime(28, AVI->EventTime, RptNum, IncrementalMinutes);
8030  if((DelayedRandMins >= 1) && !TimetableTime)
8031  {
8032  EventTime = Utilities->Format96HHMM(TTTime + TDateTime(DelayedRandMins/1440));
8033  }
8034  else if((TrainController->TTClockTime > TTTime) && !TimetableTime)
8035  {
8037  }
8038  else //((DelayedRandMins == 0) && (TTClockTime <= TTTime)) || TimetableTime
8039  {
8040  EventTime = Utilities->Format96HHMM(TTTime);
8041  }
8042  RetStr += "\nNew service joined by " + TrainController->GetRepeatHeadCode(68, AVI->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at approx. " + EventTime;
8043  Utilities->CallLogPop(2595); //above added GetRepeatHeadCode at v2.18.0 (was just AVI->OtherHeadCode before)
8044  return(RetStr);
8045  }
8046  if((AVI->Command == "Fns") || (AVI->Command == "F-nshs") || (AVI->Command == "Fns-sh")) //added at v2.15.0
8047  {
8048  TDateTime TTTime = TrainController->GetControllerTrainTime(29, AVI->EventTime, RptNum, IncrementalMinutes);
8049  if((DelayedRandMins >= 1) && !TimetableTime)
8050  {
8051  EventTime = Utilities->Format96HHMM(TTTime + TDateTime(DelayedRandMins/1440));
8052  }
8053  else if((TrainController->TTClockTime > TTTime) && !TimetableTime)
8054  {
8056  }
8057  else //((DelayedRandMins == 0) && (TTClockTime <= TTTime)) || TimetableTime
8058  {
8059  EventTime = Utilities->Format96HHMM(TTTime);
8060  }
8061  RetStr += "\nNew service finishes and forms another new service at approx. " + EventTime;
8062  Utilities->CallLogPop(2615);
8063  return(RetStr);
8064  }
8065  if(AVI->Command == "Fjo") //added at v2.15.0
8066  {
8067  TDateTime TTTime = TrainController->GetControllerTrainTime(26, AVI->EventTime, RptNum, IncrementalMinutes);
8068  if((DelayedRandMins >= 1) && !TimetableTime)
8069  {
8070  EventTime = Utilities->Format96HHMM(TTTime + TDateTime(DelayedRandMins/1440));
8071  }
8072  else if((TrainController->TTClockTime > TTTime) && !TimetableTime)
8073  {
8075  }
8076  else //((DelayedRandMins == 0) && (TTClockTime <= TTTime)) || TimetableTime
8077  {
8078  EventTime = Utilities->Format96HHMM(TTTime);
8079  }
8080  RetStr += "\nNew service finishes and joins " + TrainController->GetRepeatHeadCode(69, AVI->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at approx. " + EventTime;
8081  Utilities->CallLogPop(2605); //above added GetRepeatHeadCode at v2.18.0 (was just AVI->OtherHeadCode before)
8082  return(RetStr);
8083  }
8084  if(AVI->Command == "Frh") //added at v2.15.0
8085  {
8086  RetStr += "\nNew service finishes and remains at the location.";
8087  Utilities->CallLogPop(2606);
8088  return(RetStr);
8089  }
8090  if((AVI->FormatType == TimeLoc) && (AVI->DepartureTime > TDateTime(-1))) //departure time set
8091  {
8092  if(TimetableTime) //don't add random delay
8093  {
8094  DepTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(17, AVI->DepartureTime, RptNum, IncrementalMinutes));
8095  if(CDTFlag)
8096  {
8097  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
8098  {
8099  RetStr += "\nNew service changes direction then departs towards " + TowardsLocation + " at " + DepTime;
8100  }
8101  else
8102  {
8103  RetStr += "\nNew service changes direction then departs at " + DepTime;
8104  }
8105  }
8106  else
8107  {
8108  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
8109  {
8110  RetStr += "\nNew service departs towards " + TowardsLocation + " at " + DepTime;
8111  }
8112  else
8113  {
8114  RetStr += "\nNew service departs at " + DepTime;
8115  }
8116  }
8117  }
8118  else if(DelayedRandMins >= 1)//add random delay
8119  {
8120  DepTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(24, AVI->DepartureTime + TDateTime(DelayedRandMins/1440), RptNum, IncrementalMinutes));
8121  if(CDTFlag)
8122  {
8123  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
8124  {
8125  RetStr += "\nNew service changes direction then departs towards " + TowardsLocation + " at approx. " + DepTime;
8126  }
8127  else
8128  {
8129  RetStr += "\nNew service changes direction then departs at approx. " + DepTime;
8130  }
8131  }
8132  else
8133  {
8134  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
8135  {
8136  RetStr += "\nNew service departs towards " + TowardsLocation + " at approx. " + DepTime;
8137  }
8138  else
8139  {
8140  RetStr += "\nNew service departs at approx. " + DepTime;
8141  }
8142  }
8143  }
8144  else //no random delay but may be delayed for other reasons
8145  {
8146  TDateTime TTTime = TrainController->GetControllerTrainTime(25, AVI->DepartureTime, RptNum, IncrementalMinutes);
8147  if(TrainController->TTClockTime > TTTime)
8148  {
8150  }
8151  else
8152  {
8153  DepTime = Utilities->Format96HHMM(TTTime);
8154  }
8155  if(CDTFlag)
8156  {
8157  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
8158  {
8159  RetStr += "\nNew service changes direction then departs towards " + TowardsLocation + " at approx. " + DepTime;
8160  }
8161  else
8162  {
8163  RetStr += "\nNew service changes direction then departs at approx. " + DepTime;
8164  }
8165  }
8166  else
8167  {
8168  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
8169  {
8170  RetStr += "\nNew service departs towards " + TowardsLocation + " at approx. " + DepTime;
8171  }
8172  else
8173  {
8174  RetStr += "\nNew service departs at approx. " + DepTime;
8175  }
8176  }
8177  }
8178  Utilities->CallLogPop(2236);
8179  return(RetStr);
8180  }
8181  }
8182  Utilities->CallLogPop(2208);
8183  return(RetStr); //if reach here then RetStr doesn't change
8184 }
8185 
8186 // ---------------------------------------------------------------------------
8187 
8189 // Enter with Ptr pointing to first action to be listed (i.e. next action)
8190 // If there are actions to be skipped but a departure is awaited (SkippedDeparture = true) then after the departure Ptr moves forward by SkipPtrValue
8191 {
8192  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString(Ptr - &TrainDataEntryPtr->ActionVector.front()) +
8193  ",FloatingTimetableString" + "," + HeadCode);
8194  AnsiString RetStr = "", PartStr = "";
8195  int Count = 0;
8196  bool SkipDep = false, SkipDepActedOn = false; //SkipDepActedOn ensures only one SkipDep acted on
8197  AnsiString LocName = Ptr->LocationName;
8198 
8199  if((Ptr->Command != "") && (Ptr->Command[1] == 'S') && (TrainMode == Timetable))
8200  // can start in signaller control so exclude this
8201  {
8202  throw Exception("Error - start entry in FloatingTimetableString");
8203  }
8204  TActionVectorEntry *EntryPtr = Ptr; //used in TimeTimeLoc check later
8205  bool FirstPass = true;
8206  Ptr--; // because incremented at start of loop
8207 
8208  // different first TimeTimeLoc display if in signaller control
8209  do
8210  {
8211  Ptr++;
8212  AnsiString TrainLoc = ""; //moved to here from below at v2.20.1 when added last 2 conditions in first PassTime check below (see Albie Vowles' email of 25/07/24)
8213  if((Ptr->FormatType == Repeat) || TimetableFinished)
8214  {
8215  break;
8216  }
8217  if((Ptr->FormatType == TimeTimeLoc) && FirstPass)
8218  {
8219 // AnsiString TrainLoc = ""; moved from here at v2.20.1 - see above
8220  if(TrainMode == Timetable)
8221  {
8222  if(TrainAtLocation(1, TrainLoc) && (TrainLoc == Ptr->LocationName) && (Ptr == EntryPtr)) //added '&& (Ptr == EntryPtr)' at v2.6.0 when allow multiple same location entries
8223  {
8224  PartStr = Utilities->Format96HHMM(GetTrainTime(33, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
8225  if((LocName == Ptr->LocationName) && (LocName != "") && SkippedDeparture && !SkipDepActedOn)
8226  {
8227  SkipDep = true; //0 for incremental minutes because don't reduce the departure time when later actions have been skipped
8228  }
8229  }
8230  else if(Ptr->ArrivalTime == Ptr->DepartureTime)
8231  {
8232  PartStr = Utilities->Format96HHMM(GetTrainTime(34, Ptr->ArrivalTime)) + ": Arrive & depart from " + Ptr->LocationName;
8233  }
8234  else
8235  {
8236  PartStr = Utilities->Format96HHMM(GetTrainTime(16, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName + '\n' +
8237  Utilities->Format96HHMM(GetTrainTime(17, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
8238  Count++; // because there are 2 entries
8239  }
8240  }
8241  else // TrainMode == Signaller
8242  {
8243  if(DepartureTimeSet)
8244  {
8245  PartStr = Utilities->Format96HHMM(GetTrainTime(37, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
8246  if((LocName == Ptr->LocationName) && (LocName != "") && SkippedDeparture && !SkipDepActedOn)
8247  {
8248  SkipDep = true; //0 for incremental minutes because don't reduce the departure time when later actions have been skipped
8249  }
8250  }
8251  else if(Ptr->ArrivalTime == Ptr->DepartureTime)
8252  {
8253  PartStr = Utilities->Format96HHMM(GetTrainTime(38, Ptr->ArrivalTime)) + ": Arrive & depart from " + Ptr->LocationName;
8254  }
8255  else
8256  {
8257  PartStr = Utilities->Format96HHMM(GetTrainTime(39, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName + '\n' +
8258  Utilities->Format96HHMM(GetTrainTime(40, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
8259  Count++; // because there are 2 entries
8260  }
8261  }
8262  }
8263  else if((Ptr->FormatType == TimeTimeLoc) && !FirstPass)
8264  {
8265  AnsiString TrainLoc = "";
8266  if((TrainAtLocation(2, TrainLoc)) && (TrainLoc == Ptr->LocationName) && (Ptr == EntryPtr)) //added '&& (Ptr == EntryPtr)' at v2.6.0 when allow multiple same location entries
8267  {
8268  PartStr = Utilities->Format96HHMM(GetTrainTime(41, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
8269  if((LocName == Ptr->LocationName) && (LocName != "") && SkippedDeparture && !SkipDepActedOn)
8270  {
8271  SkipDep = true; //0 for incremental minutes because don't reduce the departure time when later actions have been skipped
8272  }
8273  }
8274  else if(Ptr->ArrivalTime == Ptr->DepartureTime)
8275  {
8276  PartStr = Utilities->Format96HHMM(GetTrainTime(42, Ptr->ArrivalTime)) + ": Arrive & depart from " + Ptr->LocationName;
8277  }
8278  else
8279  {
8280  PartStr = Utilities->Format96HHMM(GetTrainTime(43, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName + '\n' +
8281  Utilities->Format96HHMM(GetTrainTime(44, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
8282  Count++; // because there are 2 entries
8283  }
8284  }
8285  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
8286  {
8287  PartStr = Utilities->Format96HHMM(GetTrainTime(18, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName;
8288  }
8289  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
8290  {
8291  PartStr = Utilities->Format96HHMM(GetTrainTime(19, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
8292  if((LocName == Ptr->LocationName) && (LocName != "") && SkippedDeparture && !SkipDepActedOn)
8293  {
8294  SkipDep = true; //0 for incremental minutes because don't reduce the departure time when later actions have been skipped
8295  }
8296  }
8297  else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture && (TrainAtLocation(5, TrainLoc)) && (TrainLoc == Ptr->LocationName)) //added at v2.12.0 for becoming new service early (see BecomeNewService)
8298  { //note that TreatPassAsTimeLocDeparture can't be set if have SkippedDeparture
8299  //added last 2 condits at v2.20.1 as otherwise all later passes listed as departures (see Albie Vowles' email of 25/07/24)
8300  PartStr = Utilities->Format96HHMM(GetTrainTime(47, Ptr->EventTime)) + ": Depart from " + Ptr->LocationName;
8301  }
8302  else if(Ptr->FormatType == PassTime) //must come after 'else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture)'
8303  {
8304  PartStr = Utilities->Format96HHMM(GetTrainTime(30, Ptr->EventTime)) + ": Pass " + Ptr->LocationName;
8305  }
8306  else if(Ptr->Command == "Fns")
8307  {
8308  PartStr = Utilities->Format96HHMM(GetTrainTime(20, Ptr->EventTime)) + ": Form new service " + TrainController->GetRepeatHeadCode(15,
8309  Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;
8310  PartStr = GetNewServiceDepartureInfo(5, Ptr, RepeatNumber, Ptr->LinkedTrainEntryPtr, PartStr, true); //if there is a next service this adds the new service departure time to PartStr
8311  }
8312  else if(Ptr->Command == "F-nshs")
8313  {
8314  PartStr = Utilities->Format96HHMM(GetTrainTime(35, Ptr->EventTime)) + ": Form new service " + Ptr->NonRepeatingShuttleLinkHeadCode + " at " +
8315  Ptr->LocationName;
8316  PartStr = GetNewServiceDepartureInfo(6, Ptr, 0, Ptr->LinkedTrainEntryPtr, PartStr, true); //if there is a next service this adds the new service departure time to RetStr
8317  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
8318  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
8319  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
8320  }
8321  else if((Ptr->Command == "Fns-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not the last repeat number
8322  {
8323  PartStr = Utilities->Format96HHMM(GetTrainTime(21, Ptr->EventTime)) + ": Form new service " + TrainController->GetRepeatHeadCode(16,
8324  Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " + Ptr->LocationName;
8325  // use RepeatNumber+1 because it's the repeat number of the NEXT shuttle service that is relevant
8326  PartStr = GetNewServiceDepartureInfo(7, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, PartStr, true); //if there is a next service this adds the new service departure time to RetStr
8327  }
8328  else if((Ptr->Command == "Fns-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
8329  {
8330  PartStr = Utilities->Format96HHMM(GetTrainTime(22, Ptr->EventTime)) + ": Form new service " + Ptr->NonRepeatingShuttleLinkHeadCode,
8331  +" at " + Ptr->LocationName;
8332  PartStr = GetNewServiceDepartureInfo(8, Ptr, 0, Ptr->NonRepeatingShuttleLinkEntryPtr, PartStr, true); //if there is a next service this adds the new service departure time to RetStr
8333  }
8334  else if((Ptr->Command == "Frh-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not the last repeat number
8335  {
8336  PartStr = Utilities->Format96HHMM(GetTrainTime(23, Ptr->EventTime)) + ": Form new service " + TrainController->GetRepeatHeadCode(17,
8337  Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " + Ptr->LocationName;
8338  // use RepeatNumber+1 because it's the repeat number of the NEXT shuttle service that is relevant
8339  PartStr = GetNewServiceDepartureInfo(9, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, PartStr, true); //if there is a next service this adds the new service departure time to RetStr
8340  }
8341  else if((Ptr->Command == "Frh-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
8342  {
8343  PartStr = "Terminate at " + Ptr->LocationName;
8344  }
8345  else if(Ptr->Command == "Frh")
8346  {
8347  PartStr = "Terminate at " + Ptr->LocationName;
8348  }
8349  else if(Ptr->Command == "Fer")
8350  {
8351  AnsiString AllowedExits = "";
8352  PartStr = Utilities->Format96HHMM(GetTrainTime(24, Ptr->EventTime)) + ": Exit railway" + TrainController->GetExitLocationAndAt(2, Ptr->ExitList, AllowedExits) + AllowedExits;
8353  }
8354  else if(Ptr->Command == "Fjo")
8355  {
8356  PartStr = Utilities->Format96HHMM(GetTrainTime(25, Ptr->EventTime)) + ": Join " + TrainController->GetRepeatHeadCode(18, Ptr->OtherHeadCode,
8357  RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;
8358  }
8359  else if(Ptr->Command == "jbo")
8360  {
8361  PartStr = Utilities->Format96HHMM(GetTrainTime(26, Ptr->EventTime)) + ": Joined by " + TrainController->GetRepeatHeadCode(19, Ptr->OtherHeadCode,
8362  RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;
8363  }
8364  else if(Ptr->Command == "fsp")
8365  {
8366  PartStr = Utilities->Format96HHMM(GetTrainTime(27, Ptr->EventTime)) + ": Front split to " + TrainController->GetRepeatHeadCode(20,
8367  Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;
8368  if(Ptr->SplitDistribution != "")
8369  {
8370  PartStr+= ", split mass%-Power% = " + Ptr->SplitDistribution;
8371  }
8372  else
8373  {
8374  PartStr+= ", split mass%-Power% = 50-50";
8375  }
8376  }
8377  else if(Ptr->Command == "rsp")
8378  {
8379  PartStr = Utilities->Format96HHMM(GetTrainTime(28, Ptr->EventTime)) + ": Rear split to " + TrainController->GetRepeatHeadCode(21,
8380  Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;
8381  if(Ptr->SplitDistribution != "")
8382  {
8383  PartStr+= ", split mass%-Power% = " + Ptr->SplitDistribution;
8384  }
8385  else
8386  {
8387  PartStr+= ", split mass%-Power% = 50-50";
8388  }
8389  }
8390  else if(Ptr->Command == "cdt")
8391  {
8392  PartStr = Utilities->Format96HHMM(GetTrainTime(29, Ptr->EventTime)) + ": Change direction at " + Ptr->LocationName;
8393  }
8394  else if(Ptr->Command == "dsc")
8395  {
8396  PartStr = Utilities->Format96HHMM(GetTrainTime(67, Ptr->EventTime)) + ": Change description at " + Ptr->LocationName;
8397  }
8398  else if(Ptr->Command == "cms")
8399  {
8400  PartStr = Utilities->Format96HHMM(GetTrainTime(77, Ptr->EventTime)) + ": Change maximum speed to " + Ptr->NewMaxSpeed + " at " + Ptr->LocationName;
8401  }
8402  if(RetStr != "")
8403  {
8404  RetStr = RetStr + '\n' + PartStr;
8405  }
8406  else
8407  {
8408  RetStr = PartStr;
8409  }
8410  FirstPass = false;
8411  Count++;
8412 
8413  if(SkipDep)
8414  {
8415  Ptr = &(TrainDataEntryPtr->ActionVector.at(0)) + SkipPtrValue;
8416  Ptr--; //it is incremented at the start of the next loop
8417  SkipDep = false;
8418  SkipDepActedOn = true;
8419  }
8420  }
8421  while(!TimetableFinished && (Count < 32) && ((Ptr->Command == "") || ((Ptr->Command != "") && (Ptr->Command[1] != 'F'))));
8422  // limit of 32 allows a max of 34 entries (33 + 1 for the new service departure time) (may have gone from 32 to 34 because of a TimeTimeLoc), which with track and
8423  // train status gives a max of 48 lines, at 13 pixels each, = 624 pixels & screen height has 641 so will fit comfortably. Also 34 timetable entries is as far
8424  // forward as anyone should wish to see without looking at the full timetable
8425  if(TimetableFinished)
8426  {
8427  if(TrainMode == Timetable)
8428  {
8429  RetStr = "Timetable finished";
8430  }
8431  else
8432  {
8433  RetStr = "No timetable";
8434  }
8435  }
8436  Utilities->CallLogPop(1125);
8437  return("Timetable:\n" + RetStr);
8438 }
8439 
8440 // ---------------------------------------------------------------------------
8441 
8442 void TTrain::SaveOneSessionTrain(int Caller, std::ofstream &OutFile)
8443 {
8444  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SaveOneSessionTrain" + "," + HeadCode);
8445  Utilities->SaveFileString(OutFile, HeadCode);
8448  Utilities->SaveFileInt(OutFile, StartSpeed);
8451  Utilities->SaveFileInt(OutFile, RepeatNumber);
8454  Utilities->SaveFileInt(OutFile, Mass);
8457  Utilities->SaveFileDouble(OutFile, EntrySpeed);
8464  Utilities->SaveFileDouble(OutFile, BrakeRate);
8468  Utilities->SaveFileDouble(OutFile, double(EntryTime));
8469  Utilities->SaveFileDouble(OutFile, double(ExitTimeHalf));
8470  Utilities->SaveFileDouble(OutFile, double(ExitTimeFull));
8471  Utilities->SaveFileDouble(OutFile, double(ReleaseTime));
8472  Utilities->SaveFileDouble(OutFile, double(TRSTime));
8473  Utilities->SaveFileDouble(OutFile, double(LastActionTime));
8477  Utilities->SaveFileInt(OutFile, (short)TrainMode);
8482  Utilities->SaveFileBool(OutFile, Derailed);
8484  Utilities->SaveFileBool(OutFile, Crashed);
8491  Utilities->SaveFileBool(OutFile, NotInService);
8492  Utilities->SaveFileBool(OutFile, Plotted);
8493  Utilities->SaveFileBool(OutFile, TrainGone);
8494  Utilities->SaveFileBool(OutFile, SPADFlag);
8496  Utilities->SaveFileInt(OutFile, HOffset[0]);
8497  Utilities->SaveFileInt(OutFile, HOffset[1]);
8498  Utilities->SaveFileInt(OutFile, HOffset[2]);
8499  Utilities->SaveFileInt(OutFile, HOffset[3]);
8500  Utilities->SaveFileInt(OutFile, VOffset[0]);
8501  Utilities->SaveFileInt(OutFile, VOffset[1]);
8502  Utilities->SaveFileInt(OutFile, VOffset[2]);
8503  Utilities->SaveFileInt(OutFile, VOffset[3]);
8504  Utilities->SaveFileInt(OutFile, PlotElement[0]);
8505  Utilities->SaveFileInt(OutFile, PlotElement[1]);
8506  Utilities->SaveFileInt(OutFile, PlotElement[2]);
8507  Utilities->SaveFileInt(OutFile, PlotElement[3]);
8508  Utilities->SaveFileInt(OutFile, PlotEntryPos[0]);
8509  Utilities->SaveFileInt(OutFile, PlotEntryPos[1]);
8510  Utilities->SaveFileInt(OutFile, PlotEntryPos[2]);
8511  Utilities->SaveFileInt(OutFile, PlotEntryPos[3]);
8513  Utilities->SaveFileInt(OutFile, (short)Straddle);
8514  Utilities->SaveFileInt(OutFile, NextTrainID);
8515  Utilities->SaveFileInt(OutFile, TrainID);
8516  Utilities->SaveFileInt(OutFile, LeadElement);
8517  Utilities->SaveFileInt(OutFile, LeadEntryPos);
8518  Utilities->SaveFileInt(OutFile, LeadExitPos);
8519  Utilities->SaveFileInt(OutFile, MidElement);
8520  Utilities->SaveFileInt(OutFile, MidEntryPos);
8521  Utilities->SaveFileInt(OutFile, MidExitPos);
8522  Utilities->SaveFileInt(OutFile, LagElement);
8523  Utilities->SaveFileInt(OutFile, LagEntryPos);
8524  Utilities->SaveFileInt(OutFile, LagExitPos);
8525  int ColourNumber;
8526 
8528  {
8529  ColourNumber = 0;
8530  }
8532  {
8533  ColourNumber = 1;
8534  }
8536  {
8537  ColourNumber = 2;
8538  }
8540  {
8541  ColourNumber = 3;
8542  }
8544  {
8545  ColourNumber = 4;
8546  }
8548  {
8549  ColourNumber = 5;
8550  }
8552  {
8553  ColourNumber = 6;
8554  }
8556  {
8557  ColourNumber = 7;
8558  }
8560  {
8561  ColourNumber = 8;
8562  }
8564  {
8565  ColourNumber = 9;
8566  }
8568  {
8569  ColourNumber = 10;
8570  }
8572  {
8573  ColourNumber = 11;
8574  }
8576  {
8577  ColourNumber = 12;
8578  }
8579  else if(BackgroundColour == clTRSBackground)
8580  {
8581  ColourNumber = 13;
8582  }
8584  {
8585  ColourNumber = 14; // added at v2.4.0
8586  }
8587  Utilities->SaveFileInt(OutFile, ColourNumber);
8588 
8589  // additional data
8590  bool ForwardHeadCode;
8591 
8592  if(HeadCodePosition[3] == HeadCodeGrPtr[3])
8593  {
8594  ForwardHeadCode = true;
8595  }
8596  // can't use 'if(HeadCodePosition[0] == HeadCodeGrPtr[0])' as HeadCodePosition[0] is set to FrontCodePtr
8597  else
8598  {
8599  ForwardHeadCode = false;
8600  }
8601  Utilities->SaveFileBool(OutFile, ForwardHeadCode);
8602 
8603  int TrainDataEntryValue = TrainDataEntryPtr - &(TrainController->TrainDataVector.at(0));
8604 
8605  Utilities->SaveFileInt(OutFile, TrainDataEntryValue);
8606  int ActionVectorEntryValue = ActionVectorEntryPtr - &(TrainDataEntryPtr->ActionVector.at(0));
8607 
8608  Utilities->SaveFileInt(OutFile, ActionVectorEntryValue);
8609  // now the marker comes next which was ****** originally but used for RestoreTimetableLocation as well some time ago (came before the asterisks)
8610  // but at v2.4.0 need to include StoppedWithoutPower, while keeping length of marker at 6, because that is tested in earlier versions
8611  // so use the last asterisk position for this - 0 for false & 1 for true
8612  // note that failed train data is handled in InterfaceUnit.cpp & stored after the performance file
8613  AnsiString Marker;
8614 
8616  {
8617  Marker = "*****1";
8618  }
8619  else
8620  {
8621  Marker = "*****0";
8622  }
8623  if(RestoreTimetableLocation == "")
8624  {
8625  Utilities->SaveFileString(OutFile, Marker);
8626  }
8627  else
8628  {
8629  AnsiString CombinedString = RestoreTimetableLocation + Marker;
8630  Utilities->SaveFileString(OutFile, CombinedString);
8631  // RestoreTimetableLocation + marker
8632  }
8633  // Note: including RestoreTimetableLocation with the marker is to correct an oversight - it should have been saved earlier
8634  Utilities->CallLogPop(1457);
8635 }
8636 
8637 // ---------------------------------------------------------------------------
8638 
8639 void TTrain::LoadOneSessionTrain(int Caller, std::ifstream &InFile)
8640 {
8641  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LoadOneSessionTrain"); // don't have headcode yet
8642  HeadCode = Utilities->LoadFileString(InFile);
8645  StartSpeed = Utilities->LoadFileInt(InFile);
8647  if(SignallerMaxSpeed < 10)
8648  {
8649  SignallerMaxSpeed = 10; // added at v0.6 to avoid low max speeds
8650  }
8652  RepeatNumber = Utilities->LoadFileInt(InFile);
8655  Mass = Utilities->LoadFileInt(InFile);
8658  {
8660  }
8661  // above added at v2.1.0 for legacy session files where value may not have been limited
8663  EntrySpeed = Utilities->LoadFileDouble(InFile);
8667  if(TimetableMaxRunningSpeed < 10)
8668  {
8669  TimetableMaxRunningSpeed = 10; // added at v0.6 to avoid low max speeds
8670  }
8672  if(MaxRunningSpeed < 10)
8673  {
8674  MaxRunningSpeed = 10; // added at v0.6 to avoid low max speeds
8675  }
8678  BrakeRate = Utilities->LoadFileDouble(InFile);
8682  EntryTime = TDateTime(Utilities->LoadFileDouble(InFile));
8683  ExitTimeHalf = TDateTime(Utilities->LoadFileDouble(InFile));
8684  ExitTimeFull = TDateTime(Utilities->LoadFileDouble(InFile));
8685  ReleaseTime = TDateTime(Utilities->LoadFileDouble(InFile));
8686  TRSTime = TDateTime(Utilities->LoadFileDouble(InFile));
8687  LastActionTime = TDateTime(Utilities->LoadFileDouble(InFile));
8696  Derailed = Utilities->LoadFileBool(InFile);
8698  Crashed = Utilities->LoadFileBool(InFile);
8705  NotInService = Utilities->LoadFileBool(InFile);
8706  Plotted = Utilities->LoadFileBool(InFile);
8707  TrainGone = Utilities->LoadFileBool(InFile);
8708  SPADFlag = Utilities->LoadFileBool(InFile);
8710  HOffset[0] = Utilities->LoadFileInt(InFile);
8711  HOffset[1] = Utilities->LoadFileInt(InFile);
8712  HOffset[2] = Utilities->LoadFileInt(InFile);
8713  HOffset[3] = Utilities->LoadFileInt(InFile);
8714  VOffset[0] = Utilities->LoadFileInt(InFile);
8715  VOffset[1] = Utilities->LoadFileInt(InFile);
8716  VOffset[2] = Utilities->LoadFileInt(InFile);
8717  VOffset[3] = Utilities->LoadFileInt(InFile);
8718  PlotElement[0] = Utilities->LoadFileInt(InFile);
8719  PlotElement[1] = Utilities->LoadFileInt(InFile);
8720  PlotElement[2] = Utilities->LoadFileInt(InFile);
8721  PlotElement[3] = Utilities->LoadFileInt(InFile);
8722  PlotEntryPos[0] = Utilities->LoadFileInt(InFile);
8723  PlotEntryPos[1] = Utilities->LoadFileInt(InFile);
8724  PlotEntryPos[2] = Utilities->LoadFileInt(InFile);
8725  PlotEntryPos[3] = Utilities->LoadFileInt(InFile);
8727  Straddle = (TStraddle)(Utilities->LoadFileInt(InFile));
8728  NextTrainID = Utilities->LoadFileInt(InFile);
8729  // will be same for all but best to save all anyway
8730  TrainID = Utilities->LoadFileInt(InFile);
8731  LeadElement = Utilities->LoadFileInt(InFile);
8732  LeadEntryPos = Utilities->LoadFileInt(InFile);
8733  LeadExitPos = Utilities->LoadFileInt(InFile);
8734  MidElement = Utilities->LoadFileInt(InFile);
8735  MidEntryPos = Utilities->LoadFileInt(InFile);
8736  MidExitPos = Utilities->LoadFileInt(InFile);
8737  LagElement = Utilities->LoadFileInt(InFile);
8738  LagEntryPos = Utilities->LoadFileInt(InFile);
8739  LagExitPos = Utilities->LoadFileInt(InFile);
8740  int ColourNumber = TColor(Utilities->LoadFileInt(InFile));
8741 
8742  if(ColourNumber == 0)
8743  {
8745  }
8746  else if(ColourNumber == 1)
8747  {
8749  }
8750  else if(ColourNumber == 2)
8751  {
8753  }
8754  else if(ColourNumber == 3)
8755  {
8757  }
8758  else if(ColourNumber == 4)
8759  {
8761  }
8762  else if(ColourNumber == 5)
8763  {
8765  }
8766  else if(ColourNumber == 6)
8767  {
8769  }
8770  else if(ColourNumber == 7)
8771  {
8773  }
8774  else if(ColourNumber == 8)
8775  {
8777  }
8778  else if(ColourNumber == 9)
8779  {
8781  }
8782  else if(ColourNumber == 10)
8783  {
8785  }
8786  else if(ColourNumber == 11)
8787  {
8789  }
8790  else if(ColourNumber == 12)
8791  {
8793  }
8794  else if(ColourNumber == 13)
8795  {
8797  }
8798  else if(ColourNumber == 14)
8799  {
8800  BackgroundColour = clTrainFailedBackground; // added at v2.4.0
8801 
8802  }
8803  // additional data
8805  // sets the BackgroundColour to the loaded value
8806  bool ForwardHeadCode = Utilities->LoadFileBool(InFile);
8807 
8808  if(ForwardHeadCode)
8809  {
8810  for(int x = 0; x < 4; x++)
8811  {
8813  }
8814  }
8815  else
8816  {
8817  for(int x = 0; x < 4; x++)
8818  {
8819  HeadCodePosition[x] = HeadCodeGrPtr[3 - x];
8820  }
8821  }
8822  // if crashed & in timetable mode then change FrontCodePtr to black, if in signaller mode then change to blue whether crashed or not
8823  if(TrainMode == Timetable)
8824  {
8825  if(Crashed)
8826  {
8828  }
8829  else
8830  {
8832  }
8833  }
8834  else
8835  {
8837  }
8839  // pick up background bitmaps, none if MidLag as no train plotted - entering at continuation
8840  if(Straddle == LeadMid)
8841  {
8842  if(LeadElement > -1)
8843  {
8845  }
8846  if(LeadElement > -1)
8847  {
8849  }
8850  if(MidElement > -1)
8851  {
8853  }
8854  if(MidElement > -1)
8855  {
8857  }
8858  }
8859  else if(Straddle == LeadMidLag)
8860  {
8861  if(LeadElement > -1)
8862  {
8864  }
8865  if(MidElement > -1)
8866  {
8868  }
8869  if(MidElement > -1)
8870  {
8872  }
8873  if(LagElement > -1)
8874  {
8876  }
8877  }
8878  int TrainDataEntryValue = Utilities->LoadFileInt(InFile);
8879 
8880  TrainDataEntryPtr = &(TrainController->TrainDataVector.at(0)) + TrainDataEntryValue;
8881  int ActionVectorEntryValue = Utilities->LoadFileInt(InFile);
8882 
8883  ActionVectorEntryPtr = &(TrainDataEntryPtr->ActionVector.at(0)) + ActionVectorEntryValue;
8884 
8885  // need to set the TrainID if arriving at a continuation but hasn't been plotted yet
8886  if(LeadElement > -1)
8887  // need to include this in case train exiting & no lead element
8888  {
8890  {
8891  Track->TrackElementAt(668, LeadElement).TrainIDOnElement = TrainID; // no need to stop gap flashing if a continuation
8892  }
8893  }
8894  AValue = sqrt(2 * PowerAtRail / Mass);
8895 
8896  AnsiString LocationAndMarker = Utilities->LoadFileString(InFile);
8897 
8898  // possible RestoreTimetableLocation + Marker, where Marker is
8899  // "*****0" for !StoppedWithoutPower and "*****1" otherwise (from v2.4.0)
8900  // Note: including RestoreTimetableLocation with the marker is to correct an oversight - RestoreTimetableLocation should have been saved earlier
8901  // added at beta v0.2e
8902  if((LocationAndMarker[1] != '*') && (LocationAndMarker.Length() > 6))
8903  // name not allowed to include the '*' character
8904  {
8905  AnsiString Location = LocationAndMarker.SubString(1, LocationAndMarker.Length() - 6);
8906  bool GiveMessagesFalse = false;
8907  bool CheckLocationsExistInRailwayTrue = true;
8908  if(TrainController->CheckLocationValidity(3, Location, GiveMessagesFalse, CheckLocationsExistInRailwayTrue))
8909  {
8910  // otherwise take no action
8911  RestoreTimetableLocation = Location;
8912  }
8913  }
8914  AnsiString Marker = LocationAndMarker.SubString(LocationAndMarker.Length() - 5, 6);
8915 
8916  StoppedWithoutPower = false;
8917  if(Marker[6] == '1')
8918  {
8919  StoppedWithoutPower = true;
8920  }
8921  Utilities->CallLogPop(1458);
8922 }
8923 
8924 // ---------------------------------------------------------------------------
8925 
8926 bool TTrain::CheckOneSessionTrain(std::ifstream &InFile)
8927 {
8929  {
8930  return(false); // HeadCode
8931 
8932  }
8933  if(!Utilities->CheckFileInt(InFile, 0, 1000000))
8934  {
8935  return(false); // RearStartElement
8936 
8937  }
8938  if(!Utilities->CheckFileInt(InFile, 0, 3))
8939  {
8940  return(false); // RearStartExitPos
8941 
8942  }
8943  if(!Utilities->CheckFileInt(InFile, 0, MaximumSpeedLimit))
8944  {
8945  return(false); // StartSpeed
8946 
8947  }
8948  if(!Utilities->CheckFileInt(InFile, 0, MaximumSpeedLimit))
8949  {
8950  return(false); // SignallerMaxSpeed
8951 
8952  }
8953  if(!Utilities->CheckFileBool(InFile))
8954  {
8955  return(false); // HoldAtLocationInTTMode
8956 
8957  }
8958  if(!Utilities->CheckFileInt(InFile, 0, 5760))
8959  {
8960  return(false); // RepeatNumber (max 96 x 60 at 1 min intervals)
8961 
8962  }
8963  if(!Utilities->CheckFileInt(InFile, 0, 5760))
8964  {
8965  return(false); // IncrementalMinutes (max 96 x 60)
8966 
8967  }
8968  if(!Utilities->CheckFileInt(InFile, 0, 1000000))
8969  {
8970  return(false); // IncrementalDigits
8971 
8972  }
8973  if(!Utilities->CheckFileInt(InFile, 0, 10000000))
8974  {
8975  return(false); // Mass
8976 
8977  }
8978  if(!Utilities->CheckFileInt(InFile, 0, 100000000))
8979  {
8980  return(false);
8981  }
8982  // FrontElementSpeedLimit - changed at v2.1.0 - effectively
8983  // not checked so as to allow for legacy session files, for new session files limit is set when loaded, see above
8984  if(!Utilities->CheckFileInt(InFile, 0, 10000000))
8985  {
8986  return(false); // FrontElementLength
8987 
8988  }
8989  if(!Utilities->CheckFileDouble(InFile))
8990  {
8991  return(false); // EntrySpeed
8992 
8993  }
8994  if(!Utilities->CheckFileDouble(InFile))
8995  {
8996  return(false); // ExitSpeedHalf
8997 
8998  }
8999  if(!Utilities->CheckFileDouble(InFile))
9000  {
9001  return(false); // ExitSpeedFull
9002 
9003  }
9004  if(!Utilities->CheckFileDouble(InFile))
9005  {
9006  return(false); // TimetableMaxRunningSpeed
9007 
9008  }
9009  if(!Utilities->CheckFileDouble(InFile))
9010  {
9011  return(false); // MaxRunningSpeed
9012 
9013  }
9014  if(!Utilities->CheckFileDouble(InFile))
9015  {
9016  return(false); // MaxExitSpeed
9017 
9018  }
9019  if(!Utilities->CheckFileDouble(InFile))
9020  {
9021  return(false); // MaxBrakeRate
9022 
9023  }
9024  if(!Utilities->CheckFileDouble(InFile))
9025  {
9026  return(false); // BrakeRate
9027 
9028  }
9029  if(!Utilities->CheckFileDouble(InFile))
9030  {
9031  return(false); // PowerAtRail
9032 
9033  }
9034  if(!Utilities->CheckFileBool(InFile))
9035  {
9036  return(false); // FirstHalfMove
9037 
9038  }
9039  if(!Utilities->CheckFileBool(InFile))
9040  {
9041  return(false); // OneLengthAccelDecel
9042 
9043  }
9044  if(!Utilities->CheckFileDouble(InFile))
9045  {
9046  return(false); // double(EntryTime)
9047 
9048  }
9049  if(!Utilities->CheckFileDouble(InFile))
9050  {
9051  return(false); // double(ExitTimeHalf)
9052 
9053  }
9054  if(!Utilities->CheckFileDouble(InFile))
9055  {
9056  return(false); // double(ExitTimeFull)
9057 
9058  }
9059  if(!Utilities->CheckFileDouble(InFile))
9060  {
9061  return(false); // double(ReleaseTime)
9062 
9063  }
9064  if(!Utilities->CheckFileDouble(InFile))
9065  {
9066  return(false); // double(TRSTime)
9067 
9068  }
9069  if(!Utilities->CheckFileDouble(InFile))
9070  {
9071  return(false); // double(LastActionTime)
9072 
9073  }
9074  if(!Utilities->CheckFileBool(InFile))
9075  {
9076  return(false); // CallingOnFlag
9077 
9078  }
9079  if(!Utilities->CheckFileBool(InFile))
9080  {
9081  return(false); // BeingCalledOn
9082 
9083  }
9084  if(!Utilities->CheckFileBool(InFile))
9085  {
9086  return(false); // DepartureTimeSet
9087 
9088  }
9089  if(!Utilities->CheckFileInt(InFile, 0, 2))
9090  {
9091  return(false); // (short)TrainMode
9092 
9093  }
9094  if(!Utilities->CheckFileBool(InFile))
9095  {
9096  return(false); // TimetableFinished
9097 
9098  }
9099  if(!Utilities->CheckFileBool(InFile))
9100  {
9101  return(false); // LastActionDelayFlag
9102 
9103  }
9104  if(!Utilities->CheckFileBool(InFile))
9105  {
9106  return(false); // SignallerRemoved
9107 
9108  }
9109  if(!Utilities->CheckFileBool(InFile))
9110  {
9111  return(false); // TerminatedMessageSent
9112 
9113  }
9114  if(!Utilities->CheckFileBool(InFile))
9115  {
9116  return(false); // Derailed
9117 
9118  }
9119  if(!Utilities->CheckFileBool(InFile))
9120  {
9121  return(false); // DerailPending
9122 
9123  }
9124  if(!Utilities->CheckFileBool(InFile))
9125  {
9126  return(false); // Crashed
9127 
9128  }
9129  if(!Utilities->CheckFileBool(InFile))
9130  {
9131  return(false); // StoppedAtBuffers
9132 
9133  }
9134  if(!Utilities->CheckFileBool(InFile))
9135  {
9136  return(false); // StoppedAtSignal
9137 
9138  }
9139  if(!Utilities->CheckFileBool(InFile))
9140  {
9141  return(false); // StoppedAtLocation
9142 
9143  }
9144  if(!Utilities->CheckFileBool(InFile))
9145  {
9146  return(false); // SignallerStopped
9147 
9148  }
9149  if(!Utilities->CheckFileBool(InFile))
9150  {
9151  return(false); // StoppedAfterSPAD
9152 
9153  }
9154  if(!Utilities->CheckFileBool(InFile))
9155  {
9156  return(false); // StoppedForTrainInFront
9157 
9158  }
9159  if(!Utilities->CheckFileBool(InFile))
9160  {
9161  return(false); // NotInService
9162 
9163  }
9164  if(!Utilities->CheckFileBool(InFile))
9165  {
9166  return(false); // Plotted
9167 
9168  }
9169  if(!Utilities->CheckFileBool(InFile))
9170  {
9171  return(false); // TrainGone
9172 
9173  }
9174  if(!Utilities->CheckFileBool(InFile))
9175  {
9176  return(false); // SPADFlag
9177 
9178  }
9179  if(!Utilities->CheckFileBool(InFile))
9180  {
9181  return(false); // TimeTimeLocArrived
9182 
9183  }
9184  if(!Utilities->CheckFileInt(InFile, 0, 15))
9185  {
9186  return(false); // HOffset[0]
9187 
9188  }
9189  if(!Utilities->CheckFileInt(InFile, 0, 15))
9190  {
9191  return(false); // HOffset[1]
9192 
9193  }
9194  if(!Utilities->CheckFileInt(InFile, 0, 15))
9195  {
9196  return(false); // HOffset[2]
9197 
9198  }
9199  if(!Utilities->CheckFileInt(InFile, 0, 15))
9200  {
9201  return(false); // HOffset[3]
9202 
9203  }
9204  if(!Utilities->CheckFileInt(InFile, 0, 15))
9205  {
9206  return(false); // VOffset[0]
9207 
9208  }
9209  if(!Utilities->CheckFileInt(InFile, 0, 15))
9210  {
9211  return(false); // VOffset[1]
9212 
9213  }
9214  if(!Utilities->CheckFileInt(InFile, 0, 15))
9215  {
9216  return(false); // VOffset[2]
9217 
9218  }
9219  if(!Utilities->CheckFileInt(InFile, 0, 15))
9220  {
9221  return(false); // VOffset[3]
9222 
9223  }
9224  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
9225  {
9226  return(false); // PlotElement[0]
9227 
9228  }
9229  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
9230  {
9231  return(false); // PlotElement[1]
9232 
9233  }
9234  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
9235  {
9236  return(false); // PlotElement[2]
9237 
9238  }
9239  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
9240  {
9241  return(false); // PlotElement[3]
9242 
9243  }
9244  if(!Utilities->CheckFileInt(InFile, 0, 3))
9245  {
9246  return(false); // PlotEntryPos[0]
9247 
9248  }
9249  if(!Utilities->CheckFileInt(InFile, 0, 3))
9250  {
9251  return(false); // PlotEntryPos[1]
9252 
9253  }
9254  if(!Utilities->CheckFileInt(InFile, 0, 3))
9255  {
9256  return(false); // PlotEntryPos[2]
9257 
9258  }
9259  if(!Utilities->CheckFileInt(InFile, 0, 3))
9260  {
9261  return(false); // PlotEntryPos[3]
9262 
9263  }
9264  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
9265  {
9266  return(false); // TrainCrashedInto
9267 
9268  }
9269  if(!Utilities->CheckFileInt(InFile, 0, 2))
9270  {
9271  return(false); // (short)Straddle
9272 
9273  }
9274  if(!Utilities->CheckFileInt(InFile, 0, 1000000))
9275  {
9276  return(false); // NextTrainID
9277 
9278  }
9279  if(!Utilities->CheckFileInt(InFile, 0, 1000000))
9280  {
9281  return(false); // TrainID
9282 
9283  }
9284  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
9285  {
9286  return(false); // LeadElement
9287 
9288  }
9289  if(!Utilities->CheckFileInt(InFile, 0, 3))
9290  {
9291  return(false); // LeadEntryPos
9292 
9293  }
9294  if(!Utilities->CheckFileInt(InFile, 0, 3))
9295  {
9296  return(false); // LeadExitPos
9297 
9298  }
9299  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
9300  {
9301  return(false); // MidElement
9302 
9303  }
9304  if(!Utilities->CheckFileInt(InFile, 0, 3))
9305  {
9306  return(false); // MidEntryPos
9307 
9308  }
9309  if(!Utilities->CheckFileInt(InFile, 0, 3))
9310  {
9311  return(false); // MidExitPos
9312 
9313  }
9314  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
9315  {
9316  return(false); // LagElement
9317 
9318  }
9319  if(!Utilities->CheckFileInt(InFile, 0, 3))
9320  {
9321  return(false); // LagEntryPos
9322 
9323  }
9324  if(!Utilities->CheckFileInt(InFile, 0, 3))
9325  {
9326  return(false); // LagExitPos
9327 
9328  }
9329  if(!Utilities->CheckFileInt(InFile, 0, 14))
9330  {
9331  return(false);
9332  }
9333  // Background colour number //14 is failed colour at v2.4.0
9334  if(!Utilities->CheckFileBool(InFile))
9335  {
9336  return(false); // ForwardHeadCode
9337 
9338  }
9339  if(!Utilities->CheckFileInt(InFile, 0, 10000))
9340  {
9341  return(false); // TrainDataEntryValue
9342 
9343  }
9344  if(!Utilities->CheckFileInt(InFile, 0, 10000))
9345  {
9346  return(false); // ActionVectorEntryValue
9347 
9348  }
9350  {
9351  return(false); // End of train marker + possible RestoreTimetableLocation
9352 
9353  }
9354  // and StoppedWithoutPower flag
9355  return(true);
9356 }
9357 
9358 // ---------------------------------------------------------------------------
9359 
9360 void TTrain::PlotTrainInZoomOutMode(int Caller, bool Flash)
9361 {
9362  // order below reflects significance so earlier shows first, as may have more than one flag set
9363  // only plot flashing trains when Flash is true
9364 
9365 /*
9366  clCrashedBackground (TColor)0x0000FF red
9367  clDerailedBackground (TColor)0x0000FF red
9368  clSPADBackground (TColor)0x00FFFF yellow
9369  clTrainFailedBackground (TColor)0x0066FF orange new at v2.4.0
9370  clCallOnBackground (TColor)0xFF33FF light magenta
9371  clSignalStopBackground (TColor)0x00FF66 green
9372  clBufferAttentionNeeded (TColor)0xFFFF00 cyan
9373  clStationStopBackground (TColor)0xCCFFCC pale green
9374  clTRSBackground (TColor)0xFFCCFF light pink
9375  clBufferStopBackground (TColor)0xFFFFCC pale cyan
9376  clStoppedTrainInFront (TColor)0xFF9999 lavender blue
9377  clSignallerStopped (TColor)0x99CCFF caramel
9378  clNormalBackground (TColor)0xCCCCCC grey
9379 */
9380 
9381  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotTrainInZoomOutMode" + "," + HeadCode);
9382  bool HideFlashingTrain = true;
9383  // hide it when Flash false so it blinks on and off
9384  // if don't hide it it stays displayed all the time
9385  Graphics::TBitmap *SmallTrainBitmap;
9386 
9387  // NB ensure retain same order as zoomed in order so colours correspond
9389  {
9390  TrainController->CrashWarning = true;
9391  SmallTrainBitmap = RailGraphics->smRed;
9392  }
9394  {
9396  SmallTrainBitmap = RailGraphics->smRed;
9397  }
9399  {
9400  TrainController->SPADWarning = true;
9401  SmallTrainBitmap = RailGraphics->smYellow;
9402  }
9404  {
9406  SmallTrainBitmap = RailGraphics->smOrange;
9407  }
9409  {
9411  SmallTrainBitmap = RailGraphics->smMagenta;
9412  }
9414  {
9416  SmallTrainBitmap = RailGraphics->smBrightGreen;
9417  }
9419  {
9421  SmallTrainBitmap = RailGraphics->smCyan;
9422  }
9424  {
9425  SmallTrainBitmap = RailGraphics->smPaleGreen;
9426  HideFlashingTrain = false;
9427  }
9429  {
9430  SmallTrainBitmap = RailGraphics->smCyan;
9431  HideFlashingTrain = false;
9432  }
9434  {
9435  SmallTrainBitmap = RailGraphics->smLightBlue;
9436  HideFlashingTrain = false;
9437  }
9439  {
9440  SmallTrainBitmap = RailGraphics->smCaramel;
9441  HideFlashingTrain = false;
9442  }
9443  else
9444  {
9445  SmallTrainBitmap = RailGraphics->smBlack; // moving
9446  HideFlashingTrain = false;
9447  }
9448  // now plot the new train
9449  // just plot lead & mid, unless lead == -1 in which case plot mid & lag
9450  if((LeadElement > -1) && (!HideFlashingTrain || Flash))
9451  {
9452  Display->PlotSmallOutput(4, Track->TrackElementAt(441, LeadElement).HLoc * 4, Track->TrackElementAt(442, LeadElement).VLoc * 4, SmallTrainBitmap);
9453  }
9454  if((MidElement > -1) && (!HideFlashingTrain || Flash))
9455  {
9456  Display->PlotSmallOutput(5, Track->TrackElementAt(443, MidElement).HLoc * 4, Track->TrackElementAt(444, MidElement).VLoc * 4, SmallTrainBitmap);
9457  }
9458  if((LeadElement == -1) && (LagElement > -1) && (!HideFlashingTrain || Flash))
9459  {
9460  Display->PlotSmallOutput(6, Track->TrackElementAt(445, LagElement).HLoc * 4, Track->TrackElementAt(446, LagElement).VLoc * 4, SmallTrainBitmap);
9461  }
9465  Utilities->CallLogPop(1459);
9466 }
9467 
9468 // ---------------------------------------------------------------------------
9469 
9471 {
9472  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",UnplotTrainInZoomOutMode," + AnsiString(TrainID) + "," + HeadCode);
9473  if(!Display->ZoomOutFlag)
9474  {
9475  Utilities->CallLogPop(1304);
9476  return;
9477  }
9478  for(int y = 0; y < 3; y++)
9479  {
9480  if(OldZoomOutElement[y] > -1)
9481  {
9482  bool FoundFlag = false;
9483  TTrackElement ATElement = Track->TrackElementAt(717, OldZoomOutElement[y]);
9484  TTrackElement IATElement1, IATElement2;
9485  // default elements to begin with
9486  Display->PlotSmallOutput(7, ATElement.HLoc * 4, ATElement.VLoc * 4, RailGraphics->smSolidBgnd); // plot the blank
9487  TTrack::TIMPair IMPair = Track->GetVectorPositionsFromInactiveTrackMap(14, ATElement.HLoc, ATElement.VLoc, FoundFlag);
9488  // Note, have to plot inactives before track because track has to overwrite NamedLocationElements
9489  if(FoundFlag)
9490  {
9491  IATElement1 = Track->InactiveTrackElementAt(87, IMPair.first);
9492  Display->PlotSmallOutput(8, IATElement1.HLoc * 4, IATElement1.VLoc * 4, IATElement1.SmallGraphicPtr);
9493  if(IMPair.first != IMPair.second)
9494  {
9495  IATElement2 = Track->InactiveTrackElementAt(88, IMPair.second);
9496  Display->PlotSmallOutput(9, IATElement2.HLoc * 4, IATElement2.VLoc * 4, IATElement2.SmallGraphicPtr);
9497  }
9498  }
9499  Display->PlotSmallOutput(10, ATElement.HLoc * 4, ATElement.VLoc * 4, ATElement.SmallGraphicPtr);
9500  }
9501  }
9502  Utilities->CallLogPop(1305);
9503 }
9504 
9505 // ---------------------------------------------------------------------------
9506 
9507 bool TTrain::TrainAtLocation(int Caller, AnsiString &LocationName)
9508 {
9509  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainAtLocation" + "," + HeadCode);
9510  LocationName = "";
9511  if(!RevisedStoppedAtLoc())
9512  {
9513  Utilities->CallLogPop(1398);
9514  return(false);
9515  }
9516  if(LeadElement > -1)
9517  {
9519  }
9520  if((LocationName == "") && (MidElement > -1))
9521  {
9522  LocationName = Track->TrackElementAt(682, MidElement).ActiveTrackElementName;
9523  }
9524  if((LocationName == "") && (LagElement > -1))
9525  {
9526  LocationName = Track->TrackElementAt(683, LagElement).ActiveTrackElementName;
9527  }
9528  if(LocationName == "")
9529  {
9530  throw Exception("Error - Location name not set in TrainAtLocation");
9531  }
9532  Utilities->CallLogPop(1399);
9533  return(true);
9534 }
9535 
9536 // ---------------------------------------------------------------------------
9537 
9538 void TTrain::PlotTrain(int Caller, TDisplay *Disp)
9539 {
9540  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotTrain" + "," + HeadCode);
9541  for(int x = 0; x < 4; x++)
9542  {
9543  PlotTrainGraphic(7, x, Disp);
9544  }
9546  Utilities->CallLogPop(647);
9547 }
9548 
9549 // ---------------------------------------------------------------------------
9550 
9551 void TTrain::WriteTrainToImage(int Caller, Graphics::TBitmap *Bitmap)
9552 {
9553  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",WriteTrainToImage" + "," + HeadCode);
9554  for(int x = 0; x < 4; x++)
9555  {
9556  if(PlotElement[x] > -1)
9557  {
9558  Bitmap->Canvas->Draw(((Track->TrackElementAt(744, PlotElement[x]).HLoc - Track->GetHLocMin()) * 16 + HOffset[x]),
9559  ((Track->TrackElementAt(745, PlotElement[x]).VLoc - Track->GetVLocMin()) * 16 + VOffset[x]), HeadCodePosition[x]);
9560  }
9561  }
9563  {
9564  Bitmap->Canvas->Draw(LongServRefTextH - (Track->GetHLocMin() * 16), LongServRefTextV - (Track->GetVLocMin() * 16), ImageLongServRefBitmap);
9565  }
9566  Utilities->CallLogPop(1708);
9567 }
9568 
9569 // ---------------------------------------------------------------------------
9570 
9571 bool TTrain::LinkOccupied(int Caller, int TrackVectorPosition, int LinkNumber) // added at v1.2.0
9572 {
9573  // return true for any part of train occupying LinkNumber at TrackVectorPosition, false for anything else, including no LinkNumber & no TrackVectorPosition
9574  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LinkOccupied," + AnsiString(TrackVectorPosition) + "," +
9575  AnsiString(LinkNumber) + "," + HeadCode);
9576 
9577 /* Note on Straddle: Straddle defines the actual train position wrt Lag, Mid & Lead elements at all times other than within UpdateTrain. Is only MidLag outside UpdateTrain
9578  on first entry at a continuation (with no train plotted), and that has no relevance here. In all other cases it is either LeadMid (when train fully
9579  on Lead & Mid elements) or LeadMidLag (when train straddling 3 elements).
9580 */
9581 
9582  // note that MidElement always fully occupied
9583  if((MidElement == TrackVectorPosition) && ((Track->TrackElementAt(883, TrackVectorPosition).Link[MidEntryPos] == LinkNumber) || (Track->TrackElementAt(884,
9584  TrackVectorPosition).Link[MidExitPos] == LinkNumber)))
9585  {
9586  Utilities->CallLogPop(2005);
9587  return(true);
9588  }
9589  if(Straddle == LeadMid)
9590  {
9591  if((LeadElement == TrackVectorPosition) && ((Track->TrackElementAt(885, TrackVectorPosition).Link[LeadEntryPos] == LinkNumber) ||
9592  (Track->TrackElementAt(886, TrackVectorPosition).Link[LeadExitPos] == LinkNumber)))
9593  {
9594  Utilities->CallLogPop(2006);
9595  return(true);
9596  }
9597  }
9598  else if(Straddle == LeadMidLag)
9599  {
9600  if((LeadElement == TrackVectorPosition) && (Track->TrackElementAt(887, TrackVectorPosition).Link[LeadEntryPos] == LinkNumber))
9601  // only interested in LeadEntryPos as train not occupying ExitPos yet
9602  {
9603  Utilities->CallLogPop(2007);
9604  return(true);
9605  }
9606  else if((LagElement == TrackVectorPosition) && (Track->TrackElementAt(888, TrackVectorPosition).Link[LagExitPos] == LinkNumber))
9607  // only interested in LagExitPos as train has left EntryPos
9608  {
9609  Utilities->CallLogPop(2008);
9610  return(true);
9611  }
9612  }
9613  Utilities->CallLogPop(2009);
9614  return(false);
9615 }
9616 
9617 // ---------------------------------------------------------------------------
9618 
9619 float TTrain::CalcTimeToAct(int Caller, float &TimeToExit, THVShortPair &ExitPair) // only called for running trains.
9624 // TimeToExit & ExitPair added for multiplayer
9625 {
9626  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CalcTimeToAct, " + HeadCode);
9627  int DistanceToRedSignal = 0, DistanceToExit = -1;
9628  float TimeToAct = 0, LastTimeToExit = TimeToExit;
9629  TimeToExit = -1;
9630  ExitPair.first = -1;
9631  ExitPair.second = -1;
9632  float MinsEarly = 0; //added at v2.6.1
9633  TDateTime DepartureTime; //added at v2.6.1 //ArrivalTime used instead of this at v2.9.0 but still calculate it in case need it later for some reason
9634  TDateTime ArrivalTime; //added at v2.9.0 as MinsEarly used DepartureTime which wasn't correct
9635  float TempTTE;
9636 
9637  if(TrainFailed && Stopped() && (TrainMode != Signaller))
9638  {
9639  Utilities->CallLogPop(2147);
9640  return(0); // time to act now, time to exit set above
9641  }
9642  if(SignallerStopped)
9643  {
9644  Utilities->CallLogPop(2080);
9645  return(-1); //time to exit set above
9646  }
9647  if(BeingCalledOn) //added at v2.7.0 so zero time to act cancelled right away
9648  {
9649  Utilities->CallLogPop(2266);
9650  return(-1); // time to exit set above
9651  }
9652 
9653  // check if exiting at a continuation, if so there's no action time needed but still need exit time & ExitPair
9654  if((LeadElement == -1) && (MidElement == -1) && (LagElement > -1) && (Track->TrackElementAt(1411, LagElement).TrackType == Continuation)
9655  /*&& (ExitSpeedFull > 1)*/) //LagElement is the exit //ExitSpeedFull removed at v2.12.0 because of Cameron Neasom's error 28/01/22 - braking as entered continuation
9656  {
9657  if(Straddle == LeadMidLag) //only half of rear train element on exit, 0.5 lengths to exit
9658  {
9659  if(ExitSpeedFull > 0) //added at v2.12.0 because of Cameron Neasom's error 28/01/22
9660  {
9661  TempTTE = (0.5 * Track->TrackElementAt(1412, LagElement).Length01) * 3.6 / 60 / ExitSpeedFull;
9662  if(TempTTE < LastTimeToExit)
9663  {
9664  TimeToExit = TempTTE; //else leave as is, don't want a sudden increase in time
9665  }
9666  else
9667  {
9668  TimeToExit = LastTimeToExit;
9669  }
9670  }
9671  else
9672  {
9673  TimeToExit = LastTimeToExit;
9674  }
9675  ExitPair.first = Track->TrackElementAt(1413, LagElement).HLoc;
9676  ExitPair.second = Track->TrackElementAt(1414, LagElement).VLoc;
9677  Utilities->CallLogPop(2342);
9678  return(-1);
9679  }
9680  else
9681  {
9682  TimeToExit = 0; //all train exited
9683  ExitPair.first = Track->TrackElementAt(1415, LagElement).HLoc;
9684  ExitPair.second = Track->TrackElementAt(1416, LagElement).VLoc;
9685  Utilities->CallLogPop(2343);
9686  return(-1);
9687  }
9688  }
9689  if((LeadElement == -1) && (MidElement > -1) && (Track->TrackElementAt(1417, MidElement).TrackType == Continuation)/* && (ExitSpeedFull > 1)*/)
9690  //here MidElement is the exit //ExitSpeedFull removed at v2.12.0 because of Cameron Neasom's error 28/01/22 - braking as entered continuation
9691  {
9692  if(Straddle == LeadMidLag) //front element of train half off the exit, 1.5 lengths to exit
9693  {
9694  if(ExitSpeedFull > 0) //added at v2.12.0 because of Cameron Neasom's error 28/01/22
9695  {
9696  TempTTE = (1.5 * Track->TrackElementAt(1418, MidElement).Length01) * 3.6 / 60 / ExitSpeedFull;
9697  if(TempTTE < LastTimeToExit)
9698  {
9699  TimeToExit = TempTTE; //else leave as is, don't want a sudden increase in time
9700  }
9701  else
9702  {
9703  TimeToExit = LastTimeToExit;
9704  }
9705  }
9706  else
9707  {
9708  TimeToExit = LastTimeToExit;
9709  }
9710  ExitPair.first = Track->TrackElementAt(1419, MidElement).HLoc;
9711  ExitPair.second = Track->TrackElementAt(1420, MidElement).VLoc;
9712  Utilities->CallLogPop(2344);
9713  return(-1);
9714  }
9715  else //front element of train fully off the exit, one length to exit
9716  {
9717  if(ExitSpeedFull > 0) //added at v2.12.0 because of Cameron Neasom's error 28/01/22
9718  {
9719  TempTTE = (Track->TrackElementAt(1421, MidElement).Length01) * 3.6 / 60 / ExitSpeedFull;
9720  if(TempTTE < LastTimeToExit)
9721  {
9722  TimeToExit = TempTTE; //else leave as is, don't want a sudden increase in time
9723  }
9724  else
9725  {
9726  TimeToExit = LastTimeToExit;
9727  }
9728  }
9729  else
9730  {
9731  TimeToExit = LastTimeToExit;
9732  }
9733  ExitPair.first = Track->TrackElementAt(1422, MidElement).HLoc;
9734  ExitPair.second = Track->TrackElementAt(1423, MidElement).VLoc;
9735  Utilities->CallLogPop(2345);
9736  return(-1);
9737  }
9738  }
9739  if(LeadElement > -1)
9740  {
9742  /* && (ExitSpeedFull > 1)*/) //LeadElement is the exit. If LeadMidLag have lead half on exit or if LeadMid have lead full on exit
9743  { //if LeadMidLag have 2.5 lengths to exit, else have 2 lengths to exit
9744  //ExitSpeedFull removed at v2.12.0 because of Cameron Neasom's error 28/01/22 - braking as entered continuation
9745  if(Straddle == LeadMidLag)
9746  {
9747  if(ExitSpeedFull > 0) //added at v2.12.0 because of Cameron Neasom's error 28/01/22
9748  {
9749  TempTTE = (2.5 * Track->TrackElementAt(1426, LeadElement).Length01) * 3.6 / 60 / ExitSpeedFull;
9750  if(TempTTE < LastTimeToExit)
9751  {
9752  TimeToExit = TempTTE; //else leave as is, don't want a sudden increase in time
9753  }
9754  else
9755  {
9756  TimeToExit = LastTimeToExit;
9757  }
9758  }
9759  else
9760  {
9761  TimeToExit = LastTimeToExit;
9762  }
9763  ExitPair.first = Track->TrackElementAt(1427, LeadElement).HLoc;
9764  ExitPair.second = Track->TrackElementAt(1428, LeadElement).VLoc;
9765  Utilities->CallLogPop(2346);
9766  return(-1);
9767  }
9768  else
9769  {
9770  if(ExitSpeedFull > 0) //added at v2.12.0 because of Cameron Neasom's error 28/01/22
9771  {
9772  TempTTE = (2 * Track->TrackElementAt(1429, LeadElement).Length01) * 3.6 / 60 / ExitSpeedFull;
9773  if(TempTTE < LastTimeToExit)
9774  {
9775  TimeToExit = TempTTE; //else leave as is, don't want a sudden increase in time
9776  }
9777  else
9778  {
9779  TimeToExit = LastTimeToExit;
9780  }
9781  }
9782  else
9783  {
9784  TimeToExit = LastTimeToExit;
9785  }
9786  ExitPair.first = Track->TrackElementAt(1430, LeadElement).HLoc;
9787  ExitPair.second = Track->TrackElementAt(1431, LeadElement).VLoc;
9788  Utilities->CallLogPop(2347);
9789  return(-1);
9790  }
9791  }
9792  }
9793 //here if LeadElement > -1 and its forward connection also > -1
9794 
9795  // calc distance to next red signal
9796  if(!Stopped() || RevisedStoppedAtLoc())
9797  {
9798  int FirstPosToBeMeasured = Track->TrackElementAt(953, LeadElement).Conn[LeadExitPos];
9799  int FirstEntryPos = Track->TrackElementAt(954, LeadElement).ConnLinkPos[LeadExitPos];
9800  if((Straddle == LeadMidLag) && (TrainMode == Timetable))
9801 /* In TTMode it's important to set the first element to be measured ahead of the lead element only when the train fully on
9802  2 elements. Otherwise, if the train is only half on the lead element and approaching a station stop where the platform doesn't
9803  extend beyond the lead element stop point, the element ahead of the lead element is not a location whereas the ActionVector
9804  still points to the station stop location. In these circumstances the train hasn't yet stopped, so the dwell time at the
9805  stop isn't calculated, and the station to be stopped at isn't found as a future stop and nor are any other future stops
9806  because the ActionVector name never matches a future station. Hence all dwell times are omitted until the train lands fully
9807  on two elements. To avoid this when Straddle is LeadMidLag the first element to be measured is set to the lead element, so
9808  before the train has stopped the current station is still recognised as a future stop.
9809  In signaller mode stops don't count, and if pass stop signal command is given then when have LeadMidLag the current element
9810  becomes the signal, and the time to act indication becomes 'NOW'.
9811 */
9812  {
9813  FirstPosToBeMeasured = LeadElement;
9814  FirstEntryPos = LeadEntryPos;
9815  }
9816  float CurrentStopTime; // set to 0 at start of function
9817  float LaterStopTime; // set to 0 at start of function
9818  float RecoverableTime; // set to 0 at start of function
9819  int AvTrackSpeed; // set to zero at start of function
9820  bool SigControlAndCanPassRedSignal = ((TrainMode == Signaller) && AllowedToPassRedSignal);
9821  DistanceToRedSignal = TrainController->CalcDistanceToRedSignalandStopTime(0, FirstPosToBeMeasured, FirstEntryPos, SigControlAndCanPassRedSignal,
9822  ActionVectorEntryPtr, HeadCode, TrainID, CurrentStopTime, LaterStopTime, RecoverableTime, AvTrackSpeed, DistanceToExit, ExitPair);
9823 //at this point can't have both DistanceToRedSignal and DistanceToExit both set. Either both will be unset (-1) or one will be set.
9824 //Therefore since need to calculate the time for each in the same way use a generic
9825 
9826  if((DistanceToRedSignal == -1) && (DistanceToExit == -1))// both unset so no action needed
9827  {
9828  TimeToExit = -1;
9829  Utilities->CallLogPop(2076);
9830  return(-1);
9831  }
9832 //else one or other is set
9833  bool DistanceToRedSignalSet = (DistanceToRedSignal > -1);
9834  bool DistanceToExitSet = (DistanceToExit > -1);
9835  int GenericDistance = DistanceToRedSignal;
9836  if(DistanceToExitSet)
9837  {
9838  GenericDistance = DistanceToExit;
9839  }
9840 /* Have MinsDelayed; pos or neg,
9841  CurrentStopTime; pos or zero
9842  LaterStopTime; pos or zero
9843  RecoverableTime; pos or zero
9844 
9845  & from these calculate TotalStopTime. noting that:
9846  If stopped CurrentStopTime automatically adjusts for all early running and for as much late running as possible
9847  RecoverableTime always < LaterStopTime or both zero
9848  can't subtract more than RecoverableTime (MinsDelayed > 0)
9849  only subtract from LaterStopTime, not CurrentTime (MinsDelayed > 0)
9850  only subtract from LaterStopTime if LaterStopTime > 0 (MinsDelayed > 0)
9851  only add to LaterStopTime if LaterStopTime > 0 (MinsDelayed < 0)
9852  if running early & stopped at location CurrentStopTime will automatically include the excess
9853 */
9854  float TimeToSubtract, TotalStopTime;
9855  if(MinsDelayed > RecoverableTime)
9856  {
9857  TimeToSubtract = RecoverableTime;
9858  }
9859  else
9860  {
9861  TimeToSubtract = MinsDelayed; // may be negative;
9862  }
9863  if((AvTrackSpeed > 0) && (DistanceToStationStop <= GenericDistance) && (DistanceToStationStop > 0)) //protection against div by zero, not needed of no stop
9864  //before red signal, DistanceToStationStop != 0 as set to 0 if invalid
9865  //added at v2.6.1, DistanceToStationStop is calculated in SetTrainMovementValues, AvTrackSpeed is average to next red signal, but should be ok to use for
9866  //next station stop
9867  //after 2.7.0 changed (DistanceToStationStop < DistanceToRedSignal) to (DistanceToStationStop <= DistanceToRedSignal) because often have departure signal
9868  //next to the stop platform and if so the two distances are the same and the station stop time isn't included. Also after 2.7.0 used GetRepeatTime... to calc
9869  //departure time because ActionVectorEntryPtr->DepartureTime is the base time and therefore incorrect for repeats.
9870  //first find departure time from the next stop
9871  //at v2.9.0 changed MinsEarly calc to use ArrivalTime instead of departure time
9872  {
9873  if(ActionVectorEntryPtr->FormatType == TimeTimeLoc) //if already arrived then MinsEarly will be < 0 so becomes set to 0
9874  {
9877  MinsEarly = (double(ArrivalTime - TrainController->TTClockTime) * 86400 / 60) - (DistanceToStationStop * 3.6 / 60 / AvTrackSpeed);
9878  }
9879  else if((ActionVectorEntryPtr->FormatType == TimeLoc) && (ActionVectorEntryPtr->ArrivalTime != TDateTime(-1))) // not arrived yet
9880  {
9882  MinsEarly = (double(ArrivalTime - TrainController->TTClockTime) * 86400 / 60) - (DistanceToStationStop * 3.6 / 60 / AvTrackSpeed);
9883  if((ActionVectorEntryPtr + 1)->FormatType == TimeLoc)
9884  {
9885  // must be a departure
9886  DepartureTime = TrainController->GetRepeatTime(70, (ActionVectorEntryPtr + 1)->DepartureTime, RepeatNumber, IncrementalMinutes);
9887  }
9888  }
9889  else if((ActionVectorEntryPtr->FormatType == TimeLoc) && (ActionVectorEntryPtr->ArrivalTime == TDateTime(-1))) //already arrived
9890  {
9891  MinsEarly = 0;
9892  }
9893  if(MinsEarly < 0)
9894  {
9895  MinsEarly = 0;
9896  }
9897  }
9898  if(MinsDelayed < 0) // MinsDelayed < 0 means have arrived early at a station
9899  {
9900  if(CurrentStopTime > 0)
9901  {
9902  TotalStopTime = CurrentStopTime + LaterStopTime;
9903  }
9904  // stopped at loc, will depart on time
9905  else
9906  {
9907  TotalStopTime = LaterStopTime - MinsDelayed;
9908  }
9909  // not stopped, will depart on time at first later stop so add the delay
9910  }
9911  else if((MinsEarly > 0) && !Stopped()) //running early
9912  {
9913  TotalStopTime = LaterStopTime + MinsEarly;
9914  }
9915  else // on time or running late
9916  {
9917  if(LaterStopTime == 0)
9918  {
9919  TotalStopTime = CurrentStopTime;
9920  }
9921  // no later stops, if stopped now will depart as soon as possible,
9922  // if not stopped no stop times to add
9923  else
9924  {
9925  TotalStopTime = CurrentStopTime + LaterStopTime - TimeToSubtract; // later stops so deduct as much as can
9926  }
9927  }
9928  if(AvTrackSpeed < 30)
9929  {
9930  AvTrackSpeed = 30;
9931  }
9932  int Speed = AvTrackSpeed;
9933  if(AvTrackSpeed > int(MaxRunningSpeed))
9934  {
9935  Speed = int(MaxRunningSpeed);
9936  }
9937  if(TrainMode == Signaller)
9938  {
9939  Speed = SignallerMaxSpeed;
9940  TotalStopTime = 0;
9941  }
9942  if(DistanceToRedSignalSet)
9943  {
9944  TimeToAct = TotalStopTime + GenericDistance * 3.6 / 60 / Speed;
9945  // accel & decel taken into account in
9946  // CalcDistanceToRedSignalandStopTime
9947  // 3.6 converts Km/h to m/s & 60 converts seconds to minutes
9948  TimeToExit = -1;
9949  Utilities->CallLogPop(2079);
9950  return(TimeToAct);
9951  }
9952  else //DistanceToExitSet must be true
9953  {
9954  TimeToExit = TotalStopTime + GenericDistance * 3.6 / 60 / Speed;
9955  // accel & decel taken into account in
9956  // CalcDistanceToRedSignalandStopTime
9957  // 3.6 convertsKm/h to m/s & 60 converts seconds to minutes
9958  Utilities->CallLogPop(2370);
9959  return(-1); //no red signal so no time to act
9960  }
9961  }
9962  else // stopped not at location
9963  {
9965  {
9966  TimeToAct = 0.0;
9967  TimeToExit = -1;
9968  }
9969  if(StoppedWithoutPower) //added at v2.13.2 as this situation was missed & time to act was 0 [If train failed then covered above]
9970  {
9971  TimeToAct = -1;
9972  TimeToExit = -1;
9973  }
9974  // but if stopped at a signal & autosigs route after it then ok, provided signal not failed
9975  if(StoppedAtSignal)
9976  {
9977  int NextElement = Track->TrackElementAt(928, LeadElement).Conn[LeadExitPos];
9978  int NextEntryPos = Track->TrackElementAt(929, LeadElement).ConnLinkPos[LeadExitPos];
9979  bool NextElementFailed = Track->TrackElementAt(1548, NextElement).Failed; //added at v2.13.2
9980  int NextExitPos;
9981  if(Track->TrackElementAt(930, NextElement).TrackType == Points)
9982  {
9983  if((NextEntryPos == 0) || (NextEntryPos == 2))
9984  // leading entry point
9985  {
9986  if(Track->TrackElementAt(931, NextElement).Attribute == 0)
9987  {
9988  NextExitPos = 1;
9989  }
9990  else
9991  {
9992  NextExitPos = 3;
9993  }
9994  }
9995  else
9996  {
9997  NextExitPos = 0; // trailing entry point
9998  }
9999  }
10000  else
10001  {
10002  NextExitPos = Track->GetNonPointsOppositeLinkPos(NextEntryPos);
10003  }
10004  int NextButOneElement = Track->TrackElementAt(932, NextElement).Conn[NextExitPos];
10005  int NextButOneEntryPos = Track->TrackElementAt(933, NextElement).ConnLinkPos[NextExitPos];
10006  int RouteNumber; // holder for referenced value, not used
10007  if((AllRoutes->GetRouteTypeAndNumber(32, NextButOneElement, NextButOneEntryPos, RouteNumber) == TAllRoutes::AutoSigsRoute) && !NextElementFailed)
10008  { //NextElementFailed added at v2.13.2
10009  TimeToAct = -1;
10010  TimeToExit = -1;
10011  }
10012  }
10013  Utilities->CallLogPop(2074);
10014  return(TimeToAct);
10015  }
10016 }
10017 
10018 // ---------------------------------------------------------------------------
10019 
10021 {
10022  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainOnContinuation, " + HeadCode);
10023  if(LeadElement > -1)
10024  {
10026  {
10027  Utilities->CallLogPop(2148);
10028  return(true);
10029  }
10030  }
10031  if(MidElement > -1)
10032  {
10034  {
10035  Utilities->CallLogPop(2149);
10036  return(true);
10037  }
10038  }
10039  if(LagElement > -1)
10040  {
10042  {
10043  Utilities->CallLogPop(2150);
10044  return(true);
10045  }
10046  }
10047  Utilities->CallLogPop(2151);
10048  return(false);
10049 }
10050 
10051 // ---------------------------------------------------------------------------
10052 
10053 // ---------------------------------------------------------------------------
10054 // TTrainController
10055 // ---------------------------------------------------------------------------
10056 
10058 { //this called from ClearEverything() so called whenever load railway or load session, so whenever background changed it is called
10059  OnTimeArrivals = 0;
10060  LateArrivals = 0;
10061  EarlyArrivals = 0;
10062  OnTimePasses = 0;
10063  LatePasses = 0;
10064  EarlyPasses = 0;
10065  OnTimeExits = 0; //these 3 exits added at v2.9.2 - missed in error earlier
10066  LateExits = 0;
10067  EarlyExits = 0;
10068  OnTimeDeps = 0;
10069  LateDeps = 0;
10070  MissedStops = 0;
10071  OtherMissedEvents = 0;
10072  UnexpectedExits = 0;
10073  NumFailures = 0;
10075  AvHoursIntValue = 0;
10076  MTBFHours = 0;
10078  IncorrectExits = 0;
10079  SPADEvents = 0;
10080  SPADRisks = 0;
10081  CrashedTrains = 0;
10082  Derailments = 0;
10083  TotArrDepPass = 0;
10084  TotLateArrMins = 0;
10085  TotEarlyArrMins = 0;
10086  TotLatePassMins = 0;
10087  TotEarlyPassMins = 0;
10088  TotLateExitMins = 0; //added at v2.9.1
10089  TotEarlyExitMins = 0; //added at v2.9.1
10090  TotLateDepMins = 0;
10091  ExcessLCDownMins = 0;
10092  SkippedTTEvents = 0; //added at v2.11.0
10093  TTClockTime = 0; // added for v0.6
10095  // added at v1.3.0 to ensure false at start
10096  OpTimeToActUpdateCounter = 0; // new v2.2.0
10097  OpActionPanelVisible = false; // new v2.2.0
10098  // reset all message flags, stops them being given twice (shouldn't be needed here but add for safety) //new at v2.4.0
10099  SSHigh = false;
10100 // MRSHigh = false; removed at v2.21.0
10101 // MRSLow = false;
10102  MassHigh = false;
10103  BFHigh = false;
10104  BFLow = false;
10105  PwrHigh = false;
10106  SigSHigh = false;
10107  SigSLow = false;
10108  randomize();
10109  // to seed rand() & random() with a random number (see UpdateTrain)
10110  LongServRefFont = new TFont;
10111  LongServRefFont->Name = "Arial";
10112  LongServRefFont->Style = TFontStyles() << fsBold;
10113  LongServRefFont->Size = 8;
10114  if(Utilities->clTransparent == clB5G5R5) //white
10115  {
10116  LongServRefFont->Color = clB1G0R0; //very dark blue
10117  LongServRefFontColNumber = 0x01; //clB1G0R0
10118  NearTransparentColNumber = 0xda; // =218 decimal, was 0xd6; //clB4G5R5 - see RailGraphics->SetWebSafePalette(...
10119  BgndColNumber = 0xd7; //clB5G5R5
10120  }
10121  else if(Utilities->clTransparent == clB0G0R0) //black
10122  {
10123  LongServRefFont->Color = clB3G5R5; //cream //number 0xd5
10124  LongServRefFontColNumber = 0xd5; //clB3G5R5
10125  NearTransparentColNumber = 0xd8; // = 216 decimal, was 0x01; //clB1G0R0 - see RailGraphics->SetWebSafePalette(...
10126  BgndColNumber = 0x00; //clB0G0R0
10127  }
10128  else //clB1G0R0) dark blue
10129  {
10130  LongServRefFont->Color = clB3G5R5; //cream
10131  LongServRefFontColNumber = 0xd5; //clB3G5R5
10132  NearTransparentColNumber = 0xd9; // =217 decimal,was 0x00; //clB0G0R0 - see RailGraphics->SetWebSafePalette(...
10133  BgndColNumber = 0x01; //clB1G0R0
10134  }
10135 }
10136 
10137 // ---------------------------------------------------------------------------
10138 
10140 {
10141  for(unsigned int x = 0; x < TrainVector.size(); x++)
10142  {
10143  TrainVectorAt(32, x).DeleteTrain(4);
10144  }
10145  TrainVector.clear();
10146  delete LongServRefFont;
10147 }
10148 
10149 // ---------------------------------------------------------------------------
10150 
10151 void TTrainController::LogEvent(AnsiString Str)
10152 {
10153  AnsiString FullStr = Utilities->TimeStamp() + "," + TTClockTime.FormatString("hh:nn:ss") + "," + Str;
10154 
10155  // restrict to last 1000 entries
10156  Utilities->EventLog.push_back(FullStr);
10157  if(Utilities->EventLog.size() > 1000)
10158  {
10159  Utilities->EventLog.pop_front();
10160  }
10161 }
10162 
10163 // ---------------------------------------------------------------------------
10164 
10166 {
10167  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",Operate");
10168  bool ClockState = Utilities->Clock2Stopped;
10169  Utilities->Clock2Stopped = true;
10170  // new section dealing with Snt & Snt-sh additions
10171  // BUT don't add trains if points or route flashing [conditions added for Version 0.6 as a result of Najamuddin's error - 15/01/11] - wait until next
10172  // clock tick after stops flashing
10174  {
10175  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
10176  {
10177  TTrainDataEntry & TDEntry = TrainDataVector.at(x);
10178  const TActionVectorEntry &AVEntry0 = TDEntry.ActionVector.at(0);
10179  TActionEventType EventType = NoEvent;
10180  if(AVEntry0.Command == "Snt")
10181  {
10182  // calc below only for Snt & Snt-sh entries rather than all entries to save time
10183  const TActionVectorEntry &AVEntryLast = TDEntry.ActionVector.at(TDEntry.ActionVector.size() - 1);
10184  int IncrementalMinutes = 0;
10185  int IncrementalDigits = 0;
10186  if(AVEntryLast.FormatType == Repeat)
10187  {
10188  IncrementalMinutes = AVEntryLast.RearStartOrRepeatMins;
10189  IncrementalDigits = AVEntryLast.FrontStartOrRepeatDigits;
10190  }
10191  if((AVEntryLast.FormatType == Repeat) && (TDEntry.NumberOfTrains < 2))
10192  {
10193  throw Exception("Error - Repeat entry && less than two trains for Snt entry: " + TDEntry.HeadCode);
10194  }
10195  // see above note
10196 
10197  for(int y = 0; y < TDEntry.NumberOfTrains; y++)
10198  {
10199  TTrainOperatingData &TTOD = TDEntry.TrainOperatingDataVector.at(y);
10200  if(TTOD.RunningEntry != NotStarted)
10201  {
10202  continue;
10203  }
10204 
10205 //Multiplayer: here check for a train entering at a coupling {RearStartOrRepeatMins shows if it's a coupling or not), and can only be a Snt entry
10206 //if so and no arrival signalled yet bypass the timetabled arrival
10207 //if so and arrival signalled then start the new service, using the repeat number and headcode for the entering train
10208 //if a repeat is skipped then should be ok if it arrives later as its RunningEntry is still NotStarted
10209 
10210  if(GetRepeatTime(2, AVEntry0.EventTime, y, IncrementalMinutes) > TTClockTime)
10211  {
10212  break; // all the rest will also be greater
10213  }
10214  AnsiString TrainHeadCode = GetRepeatHeadCode(22, TDEntry.HeadCode, y, IncrementalDigits);
10215  if(AddTrain(2, AVEntry0.RearStartOrRepeatMins, AVEntry0.FrontStartOrRepeatDigits, TrainHeadCode, TDEntry.StartSpeed, TDEntry.Mass,
10216  TDEntry.MaxRunningSpeed, TDEntry.MaxBrakeRate, TDEntry.PowerAtRail, "Timetable", &TDEntry, y, IncrementalMinutes, IncrementalDigits,
10217  TDEntry.SignallerSpeed, AVEntry0.SignallerControl, EventType))
10218  {
10219  TTOD.TrainID = TrainVector.back().TrainID;
10220  TTOD.RunningEntry = Running;
10221  TrainVector.back().Description = TDEntry.FixedDescription; //added at v2.16.1
10222  }
10223  else if(EventType == FailTrainEntry)
10224  {
10225  break; // if a train can't enter no point checking any more repeats as they won't be able to enter either
10226  }
10227  }
10228  }
10229  if(AVEntry0.Command == "Snt-sh")
10230  // just start this once, shuttle repeats take care of restarts
10231  {
10232  // calc below only for Snt & Snt-sh entries rather than all entries to save time
10233  const TActionVectorEntry &AVEntryLast = TDEntry.ActionVector.at(TDEntry.ActionVector.size() - 1);
10234  int IncrementalMinutes = 0;
10235  int IncrementalDigits = 0;
10236  if(AVEntryLast.FormatType == Repeat)
10237  {
10238  IncrementalMinutes = AVEntryLast.RearStartOrRepeatMins;
10239  IncrementalDigits = AVEntryLast.FrontStartOrRepeatDigits;
10240  }
10241  if((AVEntryLast.FormatType == Repeat) && (TDEntry.NumberOfTrains < 2))
10242  {
10243  throw Exception("Error - Repeat entry && less than two trains for Snt-sh entry: " + TDEntry.HeadCode);
10244  }
10245  // see above note
10246  TTrainOperatingData &TTOD = TDEntry.TrainOperatingDataVector.at(0);
10247  if(TTOD.RunningEntry == NotStarted)
10248  {
10249  if(AVEntry0.EventTime <= TTClockTime)
10250  {
10251  if(AddTrain(3, AVEntry0.RearStartOrRepeatMins, AVEntry0.FrontStartOrRepeatDigits, TDEntry.HeadCode, TDEntry.StartSpeed, TDEntry.Mass,
10252  TDEntry.MaxRunningSpeed, TDEntry.MaxBrakeRate, TDEntry.PowerAtRail, "Timetable", &TDEntry, 0, IncrementalMinutes, IncrementalDigits,
10253  TDEntry.SignallerSpeed, false, EventType))
10254  // false for SignallerControl
10255  {
10256  TTOD.TrainID = TrainVector.back().TrainID;
10257  TTOD.RunningEntry = Running;
10258  TrainVector.back().Description = TDEntry.FixedDescription; //added at v2.16.1
10259  }
10260  else if(EventType == FailTrainEntry)
10261  {
10262  break; // if a train can't enter no point checking any more repeats as they won't be able to enter either
10263  }
10264  }
10265  }
10266  }
10267  }
10268  }
10269 
10270  // deal with running trains but abort if any vectors added, would probably be OK but don't risk a vector reallocation disrupting the
10271  // iteration, next cycle will catch up with any other pending updates
10272  if(!TrainVector.empty())
10273  {
10274  TrainAdded = false;
10275 
10276 //elapsed time investigations
10277 
10278 //elapsed time segment
10279 //double Start, End;
10280 //AnsiString ElapsedTimeReport = "";
10281 //end elasped time segment
10282  AllRoutes->CallonVector.clear(); // this will be rebuilt during the calls to UpdateTrain
10283 //elapsed time segment
10284 //PerfLogForm->PerformanceLog(-1, "\n Train vector size: " + AnsiString(TrainVector.size()) + '\n');
10285 //PerfLogForm->PerformanceLog(-1, "Start time list");
10286 //end elapsed time segment
10287  for(unsigned int x = 0; x < TrainVector.size(); x++)
10288  {
10289 //elapsed time segment
10290 //Start = double(GetTime()) * 86400; //secs
10291 //end elapsed time segment
10292  TrainVectorAt(33, x).UpdateTrain(0);
10293 //elapsed time segment
10294 //End = double(GetTime()) * 86400;
10295 //ElapsedTimeReport = TrainVectorAt(-1, x).TrainDataEntryPtr->ServiceReference + AnsiString(" ") + AnsiString(int((End - Start) * 1000)); //msecs
10296 //PerfLogForm->PerformanceLog(-1, ElapsedTimeReport);
10297 //end elapsed time segment
10298 
10299 //end elapsed time investigations
10300 
10301  /* added HasTrainGone() condition below in v0.4c to prevent 2 trains both having TrainGone set in UpdateTrain
10302  at the same time. That caused the error Craig Weekes reported in November 2010 where 2 trains exited at the same time, and later the TrainVector
10303  iterates in reverse to erase the second train to have gone (when the first train to have gone comes before the second in TrainVector),
10304  but afterwards ReplotTrains iterates forwards and therefore replots the first train to have gone and therefore sets the TrainIDOnElement value
10305  to the exited train, with nothing to reset it. Hovering the mouse over that element with train information enabled causes an error because
10306  the track element thinks the train is still there, whereas it is missing from the TrainVector. BUT subsequently (in v2.11.1) changed RePlotTrains
10307  so it doesn't plot trains with TrainGone set, but left this is as does no harm
10308 
10309  Had another error notified by Kevin Smith on 02/01/22 where a train was manually removed in the same clock cycle as a train exited, and this caused
10310  the same error as above. Did a lot of experimenting but eventually cured it with two changes, first as above in RePlotTrains, and also below adding
10311  a break; command after one TrainHasGone() dealt with. There were introduced in v2.11.1 & seems ok now
10312 
10313  These changes should deal with any number of TrainGone flags set in the same clock cycle - from exiting, manual removal, or joins
10314  */
10315  if(TrainAdded || TrainVectorAt(35, x).HasTrainGone())
10316  {
10317  break; //only one exited train will be dealt with at a time (see below) so no point looking further
10318  }
10319  }
10320  // set warning flags (ManualLCDownAttentionWarning dealt with in InterfaceUnit)
10321  CrashWarning = false;
10322  DerailWarning = false;
10323  SPADWarning = false;
10324  CallOnWarning = false;
10325  SignalStopWarning = false;
10326  BufferAttentionWarning = false;
10327  TrainFailedWarning = false;
10328  for(int x = TrainVector.size() - 1; x >= 0; x--) // reverse because of erase
10329  {
10330  TTrain &Train = TrainVectorAt(34, x);
10331  if(Train.Crashed)
10332  // can't use background colours for crashed & derailed because same colour
10333  {
10334  CrashWarning = true;
10335  }
10336  else if(Train.Derailed)
10337  // can't use background colours for crashed & derailed because same colour
10338  {
10339  DerailWarning = true;
10340  }
10341  else if(Train.BackgroundColour == clSPADBackground)
10342  // use colour as that changes as soon as passes signal
10343  {
10344  SPADWarning = true;
10345  }
10346  else if(Train.BackgroundColour == clTrainFailedBackground)
10347  {
10348  TrainFailedWarning = true;
10349  }
10350  else if(Train.BackgroundColour == clCallOnBackground)
10351  // use colour as also stopped at signal
10352  {
10353  CallOnWarning = true;
10354  }
10355  else if(Train.BackgroundColour == clSignalStopBackground)
10356  // use colour to distinguish from call-on
10357  {
10358  SignalStopWarning = true;
10359  }
10360  else if(Train.BackgroundColour == clBufferAttentionNeeded)
10361  // use colour to distinguish from ordinary buffer stop
10362  {
10363  BufferAttentionWarning = true;
10364  }
10365  if(Train.HasTrainGone())
10366  {
10367  AnsiString Loc = "";
10368  bool ElementFound = false;
10369  TTrackElement TE;
10370  if(Train.LagElement > -1)
10371  {
10372  TE = Track->TrackElementAt(531, Train.LagElement);
10373  ElementFound = true;
10374  }
10375  else if(Train.MidElement > -1)
10376  {
10377  TE = Track->TrackElementAt(779, Train.MidElement);
10378  ElementFound = true;
10379  }
10380  else if(Train.LeadElement > -1)
10381  {
10382  TE = Track->TrackElementAt(780, Train.LeadElement);
10383  ElementFound = true;
10384  }
10385  if(ElementFound)
10386  {
10387  if(TE.ActiveTrackElementName != "")
10388  {
10389  Loc = TE.ActiveTrackElementName + ", track element " + TE.ElementID;
10390  }
10391  else
10392  {
10393  Loc = "track element " + TE.ElementID;
10394  }
10395  }
10396  TActionVectorEntry *AVEntryPtr = Train.ActionVectorEntryPtr;
10397  if((Train.SignallerRemoved) || (Train.JoinedOtherTrainFlag))
10398  // need above first because may also have ActionVectorEntryPtr == "Fer"
10399  {
10400  Train.UnplotTrain(9);
10401  // added at v1.3.0 to reset signals after train removed from an autosigsroute
10403  {
10406  }
10407  // end of addition
10408  AllRoutes->RebuildRailwayFlag = true;
10409  // to force ClearandRebuildRailway at next clock tick if not in zoom-out mode, to replot LCs
10410  // correctly after a crash
10411  }
10412  else if(AVEntryPtr->Command == "Fer")
10413  {
10414  bool CorrectExit = false;
10415  if(!AVEntryPtr->ExitList.empty())
10416  {
10417  for(TNumListIterator ELIT = AVEntryPtr->ExitList.begin(); ELIT != AVEntryPtr->ExitList.end(); ELIT++)
10418  {
10419  if(*ELIT == Train.LagElement)
10420  {
10421  CorrectExit = true;
10422  }
10423  }
10424  }
10425  if(CorrectExit)
10426  {
10427  Train.LogAction(19, Train.HeadCode, "", Leave, Loc, "", AVEntryPtr->EventTime, AVEntryPtr->Warning);
10428  }
10429  else
10430  {
10431  LogActionError(38, Train.HeadCode, "", FailIncorrectExit, Loc);
10432  }
10433  }
10434  else
10435  {
10436  if(!AVEntryPtr->SignallerControl)
10437  {
10438  LogActionError(26, Train.HeadCode, "", FailUnexpectedExitRailway, Loc);
10439  Train.SendMissedActionLogs(2, -2, AVEntryPtr);
10440  // -2 is marker for send messages for all remaining actions except Fer if present
10441  }
10442  else
10443  {
10444  Train.LogAction(31, Train.HeadCode, "", SignallerLeave, Loc, "", TDateTime(0), false); // false for Warning
10445  }
10446  }
10447  Utilities->CumulativeDelayedRandMinsAllTrains += Train.CumulativeDelayedRandMinsOneTrain; //added at v2.13.0 for random delays
10448  Train.TrainDataEntryPtr->TrainOperatingDataVector.at(Train.RepeatNumber).RunningEntry = Exited;
10449  Train.DeleteTrain(1);
10450  TrainVector.erase(TrainVector.begin() + x);
10451  ReplotTrains(1, Display); //to reset ElementIDs for remaining trains when have removed a train
10452  //NB: won't plot any trains with TrainGone flag set (changed at v2.11.1)
10453  break; //added at v2.11.1 to ensure that only one train with TrainGone set is dealt with in one clock cycle
10454  }
10455  }
10456  }
10457  else
10458  {
10459  // reset all flags in case last train removed with flag set
10460  CrashWarning = false;
10461  DerailWarning = false;
10462  SPADWarning = false;
10463  CallOnWarning = false;
10464  SignalStopWarning = false;
10465  BufferAttentionWarning = false;
10466  TrainFailedWarning = false;
10467  }
10468  // update OpTimeToActMultimap
10470  {
10472  // clears entries then adds values for running trains then for continuation entries
10474  //added for multiplayer for running trains only
10475  }
10476  Utilities->Clock2Stopped = ClockState;
10477  Utilities->CallLogPop(723);
10478 }
10479 
10480 // ---------------------------------------------------------------------------
10482 {
10483  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",FinishedOperation");
10484  if(!TrainVector.empty())
10485  {
10486  for(int x = TrainVector.size() - 1; x >= 0; x--)
10487  {
10488  TrainVectorAt(50, x).DeleteTrain(2);
10489  }
10490  TrainVector.clear();
10491  }
10492  if(!TrainDataVector.empty())
10493  {
10494  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
10495  {
10496  TTrainDataEntry &TDEntry = TrainDataVector.at(x);
10497  for(int y = 0; y < TDEntry.NumberOfTrains; y++)
10498  {
10499  TTrainOperatingData &TOD = TDEntry.TrainOperatingDataVector.at(y);
10500  TOD.RunningEntry = NotStarted;
10501  TOD.TrainID = -1;
10502  TOD.EventReported = NoEvent;
10503  }
10504  }
10505  }
10506  Display->GetOutputLog1()->Caption = "";
10507  Display->GetOutputLog2()->Caption = "";
10508  Display->GetOutputLog3()->Caption = "";
10509  Display->GetOutputLog4()->Caption = "";
10510  Display->GetOutputLog5()->Caption = "";
10511  Display->GetOutputLog6()->Caption = "";
10512  Display->GetOutputLog7()->Caption = "";
10513  Display->GetOutputLog8()->Caption = "";
10514  Display->GetOutputLog9()->Caption = "";
10515  Display->GetOutputLog10()->Caption = "";
10516  Utilities->CallLogPop(1352);
10517 }
10518 
10519 // ---------------------------------------------------------------------------
10520 
10522 {
10523  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ReplotTrains");
10524  if(!TrainVector.empty())
10525  {
10526  for(unsigned int x = 0; x < TrainVector.size(); x++)
10527  {
10528  if(!TrainVectorAt(84, x).HasTrainGone()) //added at v2.11.0 to prevent plotting a train pending removal & particularly to prevent TrainElementID's being reinstated
10529  { //see Kevin Smith error information for details
10530  TrainVectorAt(51, x).PlotTrain(4, Disp);
10531  }
10532  }
10533  }
10534  Utilities->CallLogPop(724);
10535 }
10536 
10537 // ---------------------------------------------------------------------------
10538 
10539 void TTrainController::WriteTrainsToImage(int Caller, Graphics::TBitmap *Bitmap)
10540 {
10541  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",WriteTrainsToImage");
10542  if(!TrainVector.empty())
10543  {
10544  for(unsigned int x = 0; x < TrainVector.size(); x++)
10545  {
10546  TrainVectorAt(61, x).WriteTrainToImage(0, Bitmap);
10547  }
10548  }
10549  Utilities->CallLogPop(1707);
10550 }
10551 
10552 // ---------------------------------------------------------------------------
10553 
10555 {
10556  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",UnplotTrains");
10557  if(!TrainVector.empty())
10558  {
10559  for(unsigned int x = 0; x < TrainVector.size(); x++)
10560  {
10561  TrainVectorAt(52, x).UnplotTrain(10);
10562  }
10563  }
10565  Utilities->CallLogPop(725);
10566 }
10567 
10568 // ---------------------------------------------------------------------------
10569 
10570 bool TTrainController::AddTrain(int Caller, int RearPosition, int FrontPosition, AnsiString HeadCode, int StartSpeed, int Mass, double MaxRunningSpeed,
10571  double MaxBrakeRate, double PowerAtRail, AnsiString ModeStr, TTrainDataEntry *TrainDataEntryPtr, int RepeatNumber, int IncrementalMinutes,
10572  int IncrementalDigits, int SignallerSpeed, bool SignallerControl, TActionEventType &EventType)
10573 {
10574  LogEvent(AnsiString(Caller) + ",AddTrain," + AnsiString(RearPosition) + "," + AnsiString(FrontPosition) + "," + HeadCode + "," + AnsiString(StartSpeed) +
10575  "," + AnsiString(Mass) + "," + ModeStr);
10576  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",AddTrain," + AnsiString(RearPosition) + "," + AnsiString(FrontPosition) +
10577  "," + HeadCode + "," + AnsiString(StartSpeed) + "," + AnsiString(Mass) + "," + ModeStr); //at v2.11.1 dropped later headcode - was listed twice
10578 
10579  int RearExitPos = -1;
10580 
10581  for(int x = 0; x < 4; x++)
10582  {
10583  if(Track->TrackElementAt(519, RearPosition).Conn[x] == FrontPosition)
10584  {
10585  RearExitPos = x;
10586  }
10587  }
10588  if(RearExitPos == -1)
10589  {
10590  throw Exception("Error, RearExit == -1 in AddTrain");
10591  }
10592  bool ReportFlag = true;
10593 
10594  // used to stop repeated messages from CheckStartAllowable when split failed
10595  if(TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).EventReported != NoEvent)
10596  {
10597  ReportFlag = false;
10598  }
10599  if(!CheckStartAllowable(0, RearPosition, RearExitPos, HeadCode, ReportFlag, EventType))
10600  {
10601  // messages sent to performance log in CheckStartAllowable if ReportFlag true
10602  TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).EventReported = EventType;
10603  Utilities->CallLogPop(938);
10604  return(false);
10605  }
10606  TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).EventReported = NoEvent;
10607  TTrainMode TrainMode = NoMode;
10608 
10609  if(ModeStr == "Timetable")
10610  {
10611  TrainMode = Timetable;
10612  }
10613  // all else gives 'None', 'Signaller' set within program
10614 
10615  if(MaxRunningSpeed < 10)
10616  {
10617  MaxRunningSpeed = 10; // added at v0.6 to avoid low max speeds
10618  }
10619  if(SignallerSpeed < 10)
10620  {
10621  SignallerSpeed = 10; // added at v0.6 to avoid low max speeds
10622  }
10623  TTrain *NewTrain = new TTrain(0, RearPosition, RearExitPos, HeadCode, StartSpeed, Mass, MaxRunningSpeed, MaxBrakeRate, PowerAtRail, TrainMode,
10624  TrainDataEntryPtr, RepeatNumber, IncrementalMinutes, IncrementalDigits, SignallerSpeed);
10625 
10626  LogEvent("AddTrainSupplemental: Service Ref = " + TrainDataEntryPtr->ServiceReference + ", TrainID = " + AnsiString(NewTrain->TrainID)); //new at v2.11.1 so can relate headcode to ID
10627 
10628  NewTrain->ActionVectorEntryPtr = &(TrainDataEntryPtr->ActionVector.at(0));
10629  // initialise here rather than in TTrain constructor as create trains
10630  // with Null TrainDataEntryPtr when loading session trains
10631  if(SignallerControl)
10632  {
10633  NewTrain->TimetableFinished = true;
10634  NewTrain->SignallerStoppingFlag = false;
10635  NewTrain->TrainMode = Signaller;
10636  if(NewTrain->MaxRunningSpeed > NewTrain->SignallerMaxSpeed)
10637  {
10638  NewTrain->MaxRunningSpeed = NewTrain->SignallerMaxSpeed;
10639  }
10641  }
10642  // deal with starting conditions:-
10643  // unlocated Snt: just report entry & advance pointer
10644  // located Snt or Sfs: set station conditions as would if had reached stop point in Update(), & advance the ActionVectorEntryPtr
10645  // Sns doesn't need a new train
10646  if(NewTrain->ActionVectorEntryPtr->LocationName != "")
10647  // covers all above located starts
10648  // if location of Snt was a station (that is set as LocationName, i.e. not just any station) that isn't next departure station then
10649  // wouldn't have accepted the timetable
10650  {
10651  // first check if LeadElement (can't access LeadElement directly yet as not set, use FrontPosition instead) is buffers, note that
10652  // StoppedAtBuffers is set in UpdateTrain()
10653  if(Track->TrackElementAt(520, FrontPosition).TrackType == Buffers)
10654  // buffer end must be ahead of train or would have failed start position check
10655  {
10656  NewTrain->StoppedAtLocation = true;
10657  NewTrain->PlotStartPosition(0);
10659  NewTrain->LogAction(20, NewTrain->HeadCode, "", Create, NewTrain->ActionVectorEntryPtr->LocationName, "", NewTrain->ActionVectorEntryPtr->EventTime,
10660  NewTrain->ActionVectorEntryPtr->Warning);
10661  if(!SignallerControl) // don't advance if SignalControlEntry
10662  {
10663  NewTrain->ActionVectorEntryPtr++;
10664  // should be a command, could be a location departure but if so can't depart so set 'Hold' anyway
10665  }
10666  NewTrain->LastActionTime = TTClockTime;
10667  }
10668  // else a through station stop
10669  else
10670  {
10671  NewTrain->StoppedAtLocation = true;
10672  NewTrain->PlotStartPosition(10);
10674  NewTrain->LogAction(21, NewTrain->HeadCode, "", Create, NewTrain->ActionVectorEntryPtr->LocationName, "", NewTrain->ActionVectorEntryPtr->EventTime,
10675  NewTrain->ActionVectorEntryPtr->Warning);
10676  if(!SignallerControl) // don't advance if SignalControlEntry
10677  {
10678  NewTrain->ActionVectorEntryPtr++;
10679  }
10680  NewTrain->LastActionTime = TTClockTime;
10681  }
10682  }
10683  else // unlocated entry (i.e. not a stop entry, but could still be at a named location)
10684  {
10685  NewTrain->PlotStartPosition(11);
10686  TTrackElement TE = Track->TrackElementAt(530, NewTrain->RearStartElement);
10687  AnsiString Loc = "";
10688  if(TE.ActiveTrackElementName != "")
10689  {
10690  Loc = TE.ActiveTrackElementName + ", track element " + TE.ElementID;
10691  }
10692  else
10693  {
10694  Loc = "track element " + TE.ElementID;
10695  }
10696  if(TE.TrackType == Continuation)
10697  {
10698  NewTrain->LogAction(22, NewTrain->HeadCode, "", Enter, Loc, "", NewTrain->ActionVectorEntryPtr->EventTime, NewTrain->ActionVectorEntryPtr->Warning);
10699  }
10700  else
10701  {
10702  NewTrain->LogAction(23, NewTrain->HeadCode, "", Create, Loc, "", NewTrain->ActionVectorEntryPtr->EventTime, NewTrain->ActionVectorEntryPtr->Warning);
10703  }
10704  if(!SignallerControl) // don't advance if SignalControlEntry
10705  {
10706  NewTrain->ActionVectorEntryPtr++;
10707  }
10708  NewTrain->LastActionTime = TTClockTime;
10709  // no need to set LastActionTime for an unlocated entry
10710  }
10711  // cancel a wrong-direction route if either element of train starts on one
10712  if(NewTrain->LeadElement > -1)
10713  {
10714  NewTrain->CheckAndCancelRouteForWrongEndEntry(3, NewTrain->LeadElement, NewTrain->LeadEntryPos);
10715  }
10716  if(NewTrain->MidElement > -1)
10717  {
10718  NewTrain->CheckAndCancelRouteForWrongEndEntry(4, NewTrain->MidElement, NewTrain->MidEntryPos);
10719  }
10720  // set signals for a right-direction autosigs route for either element of train on one
10721  // erase elements back to start for a non-autosigs route & check if an autosigs route immediately behind it, and if so set its signals
10722  // note that all but autosigs routes become part of a single route, so there can only be an autosigs route behind the non-autosigs route
10723  int RouteNumber = -1;
10724  bool SignalsSet = false;
10725 
10726  if(NewTrain->LeadElement > -1)
10727  {
10728  if(AllRoutes->GetRouteTypeAndNumber(13, NewTrain->LeadElement, NewTrain->LeadEntryPos, RouteNumber) == TAllRoutes::AutoSigsRoute)
10729  {
10730  // below added in place of SetRouteSignals in v2.4.0 as don't want to set signals from start of route for a new train addition
10731  int RouteStartPosition;
10732  TAllRoutes::TRouteElementPair FirstPair, SecondPair;
10733  FirstPair = AllRoutes->GetRouteElementDataFromRoute2MultiMap(21, Track->TrackElementAt(955, FrontPosition).HLoc,
10734  Track->TrackElementAt(956, FrontPosition).VLoc, SecondPair);
10735  if(FirstPair.first == RouteNumber)
10736  {
10737  RouteStartPosition = FirstPair.second;
10738  }
10739  else if(SecondPair.first == RouteNumber)
10740  {
10741  RouteStartPosition = SecondPair.second;
10742  }
10743  else
10744  {
10745  throw Exception("Error, RouteNumber not found in Route2MultiMap in 1st of 2 calls to SetAllRearwardsSignals in AddTrain");
10746  }
10747  AllRoutes->SetAllRearwardsSignals(10, 0, RouteNumber, RouteStartPosition);
10748  SignalsSet = true;
10749  // AllRoutes->GetFixedRouteAt(, RouteNumber).SetRouteSignals(); above substituted in v2.4.0
10750  }
10751  else if(RouteNumber > -1) // non-autosigsroute
10752  {
10753  TPrefDirElement TempPDE = AllRoutes->GetFixedRouteAt(181, RouteNumber).GetFixedPrefDirElementAt(194, 0);
10754  int FirstTVPos = TempPDE.GetTrackVectorPosition();
10755  int FirstELinkPos = TempPDE.GetELinkPos();
10756  while(TempPDE.GetTrackVectorPosition() != (unsigned int)(NewTrain->LeadElement))
10757  {
10758  AllRoutes->RemoveRouteElement(16, TempPDE.HLoc, TempPDE.VLoc, TempPDE.GetELink());
10759  TempPDE = AllRoutes->GetFixedRouteAt(182, RouteNumber).GetFixedPrefDirElementAt(195, 0);
10760  }
10761  if(TempPDE.GetTrackVectorPosition() == (unsigned int)(NewTrain->LeadElement))
10762  {
10763  AllRoutes->RemoveRouteElement(17, TempPDE.HLoc, TempPDE.VLoc, TempPDE.GetELink());
10764  // remove the last element under LeadElement
10765  }
10766  AllRoutes->RebuildRailwayFlag = true;
10767  // to force ClearandRebuildRailway at next clock tick if not in zoom-out mode
10768  // now deal with a rear linked autosigs route
10769  if(Track->TrackElementAt(820, FirstTVPos).Conn[FirstELinkPos] > -1)
10770  {
10771  int LinkedRouteNumber = -1;
10772  if(AllRoutes->GetRouteTypeAndNumber(17, Track->TrackElementAt(821, FirstTVPos).Conn[FirstELinkPos],
10773  Track->TrackElementAt(822, FirstTVPos).ConnLinkPos[FirstELinkPos], LinkedRouteNumber) == TAllRoutes::AutoSigsRoute)
10774  {
10775  AllRoutes->GetFixedRouteAt(169, LinkedRouteNumber).SetRouteSignals(0);
10776  // this is ok as here we are setting signals from the start of the route
10777  }
10778  }
10779  SignalsSet = true;
10780  }
10781  }
10782  if(NewTrain->MidElement > -1)
10783  // if entering at a continuation MidElement == -1
10784  {
10785  // this is included in case a train starts with LeadElement on no route and MidElement on a route
10786  if(!SignalsSet)
10787  {
10788  RouteNumber = -1;
10789  if(AllRoutes->GetRouteTypeAndNumber(14, NewTrain->MidElement, NewTrain->MidEntryPos, RouteNumber) == TAllRoutes::AutoSigsRoute)
10790  {
10791  // below added in place of SetRouteSignals in v2.4.0 as don't want to set signals from start of route for a new train addition
10792  int RouteStartPosition;
10793  TAllRoutes::TRouteElementPair FirstPair, SecondPair;
10794  FirstPair = AllRoutes->GetRouteElementDataFromRoute2MultiMap(22, Track->TrackElementAt(957, RearPosition).HLoc,
10795  Track->TrackElementAt(958, RearPosition).VLoc, SecondPair);
10796  if(FirstPair.first == RouteNumber)
10797  {
10798  RouteStartPosition = FirstPair.second;
10799  }
10800  else if(SecondPair.first == RouteNumber)
10801  {
10802  RouteStartPosition = SecondPair.second;
10803  }
10804  else
10805  {
10806  throw Exception("Error, RouteNumber not found in Route2MultiMap in 2nd of 2 calls to SetAllRearwardsSignals in AddTrain");
10807  }
10808  AllRoutes->SetAllRearwardsSignals(11, 0, RouteNumber, RouteStartPosition);
10809  SignalsSet = true;
10810  // AllRoutes->GetFixedRouteAt(, RouteNumber).SetRouteSignals(); above substituted in v2.4.0
10811  }
10812  else if(RouteNumber > -1) // non-autosigsroute
10813  {
10814  TPrefDirElement TempPDE = AllRoutes->GetFixedRouteAt(184, RouteNumber).GetFixedPrefDirElementAt(196, 0);
10815  int FirstTVPos = TempPDE.GetTrackVectorPosition();
10816  int FirstELinkPos = TempPDE.GetELinkPos();
10817  while(TempPDE.GetTrackVectorPosition() != (unsigned int)(NewTrain->MidElement))
10818  {
10819  AllRoutes->RemoveRouteElement(18, TempPDE.HLoc, TempPDE.VLoc, TempPDE.GetELink());
10820  TempPDE = AllRoutes->GetFixedRouteAt(185, RouteNumber).GetFixedPrefDirElementAt(197, 0);
10821  }
10822  if(TempPDE.GetTrackVectorPosition() == (unsigned int)(NewTrain->MidElement))
10823  {
10824  AllRoutes->RemoveRouteElement(19, TempPDE.HLoc, TempPDE.VLoc, TempPDE.GetELink());
10825  // remove the last element under LeadElement
10826  }
10827  AllRoutes->RebuildRailwayFlag = true;
10828  // to force ClearandRebuildRailway at next clock tick if not in zoom-out mode
10829  // now deal with a rear linked autosigs route
10830  if(Track->TrackElementAt(823, FirstTVPos).Conn[FirstELinkPos] > -1)
10831  {
10832  int LinkedRouteNumber = -1;
10833  if(AllRoutes->GetRouteTypeAndNumber(19, Track->TrackElementAt(824, FirstTVPos).Conn[FirstELinkPos],
10834  Track->TrackElementAt(825, FirstTVPos).ConnLinkPos[FirstELinkPos], LinkedRouteNumber) == TAllRoutes::AutoSigsRoute)
10835  {
10836  AllRoutes->GetFixedRouteAt(170, LinkedRouteNumber).SetRouteSignals(1);
10837  // this is ok as now we are setting signals from the start of the route
10838  }
10839  }
10840  }
10841  }
10842  }
10843  TrainVector.push_back(*NewTrain);
10844  Utilities->CallLogPop(731);
10845  return(true);
10846 }
10847 
10848 // ---------------------------------------------------------------------------
10849 
10850 int TTrainController::EntryPos(int Caller, int TrainIDIn, int TrackVectorNumber)
10851 {
10852  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",EntryPos," + AnsiString(TrainIDIn) + "," +
10853  AnsiString(TrackVectorNumber));
10854  int VecPos = -1;
10855 
10856  for(unsigned int x = 0; x < TrainVector.size(); x++)
10857  {
10858  if(TrainVectorAt(1, x).TrainID == TrainIDIn)
10859  {
10860  VecPos = x;
10861  }
10862  }
10863  if(VecPos == -1)
10864  {
10865  throw Exception("Error, VecPos not set in EntryPos");
10866  }
10867  if(TrainVectorAt(2, VecPos).LeadElement == TrackVectorNumber)
10868  {
10869  Utilities->CallLogPop(734);
10870  return(TrainVectorAt(3, VecPos).LeadEntryPos);
10871  }
10872  else if(TrainVectorAt(4, VecPos).MidElement == TrackVectorNumber)
10873  {
10874  Utilities->CallLogPop(735);
10875  return(TrainVectorAt(5, VecPos).MidEntryPos);
10876  }
10877  else if(TrainVectorAt(6, VecPos).LagElement == TrackVectorNumber)
10878  {
10879  Utilities->CallLogPop(736);
10880  return(TrainVectorAt(7, VecPos).LagEntryPos);
10881  }
10882  Utilities->CallLogPop(737);
10883  return(-1);
10884 }
10885 
10886 // ---------------------------------------------------------------------------
10887 
10889 {
10890  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainVectorAtIdent," + AnsiString(TrainID));
10891  for(unsigned int x = 0; x < TrainVector.size(); x++)
10892  {
10893  if(TrainVectorAt(53, x).TrainID == TrainID)
10894  {
10895  Utilities->CallLogPop(738);
10896  return(TrainVectorAt(54, x));
10897  }
10898  }
10899  throw Exception("Error - No Train identified in TrainVectorAtIdent with ID = " + AnsiString(TrainID));
10900 }
10901 
10902 // ---------------------------------------------------------------------------
10903 
10904 bool TTrainController::TrainExistsAtIdent(int Caller, int TrainID)
10905 // return true if find the train (added at v2.4.0 as can select a removed train in
10906 // ActionsDueListBox before it updates - reported by LiWinDom in error report 23/04/20)
10907 {
10908  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainExistsAtIdent," + AnsiString(TrainID));
10909  for(unsigned int x = 0; x < TrainVector.size(); x++)
10910  {
10911  if(TrainVectorAt(69, x).TrainID == TrainID)
10912  {
10913  Utilities->CallLogPop(2152);
10914  return(true);
10915  }
10916  }
10917  Utilities->CallLogPop(2153);
10918  return(false);
10919 }
10920 
10921 // ---------------------------------------------------------------------------
10922 
10923 TDateTime TTrainController::GetControllerTrainTime(int Caller, TDateTime Time, int RepeatNumber, int IncrementalMinutes)
10924 {
10925  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetControllerTrainTime," + AnsiString(RepeatNumber) + "," +
10926  Utilities->Format96HHMMSS(Time));
10927  TDateTime RepeatTime = TrainController->GetRepeatTime(47, Time, RepeatNumber, IncrementalMinutes);
10928 
10929  Utilities->CallLogPop(2061);
10930  return(RepeatTime);
10931 }
10932 
10933 // ---------------------------------------------------------------------------
10934 
10935 AnsiString TTrainController::ContinuationEntryFloatingTTString(int Caller, TTrainDataEntry *TTDEPtr, int RepNum, int IncMins, int IncDig)
10936 // Enter with Ptr pointing to first action to be listed (i.e. next action)
10937 {
10938  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ContinuationEntryFloatingTTString" + "," + TTDEPtr->HeadCode);
10939  AnsiString RetStr = "", PartStr = "";
10940  int Count = 0;
10941  TActionVectorIterator Ptr = TTDEPtr->ActionVector.begin();
10942 
10943  Ptr--; // because incremented at start of loop
10944  do
10945  {
10946  Ptr++;
10947  if((Ptr->Command != "") && (Ptr->Command[1] == 'S'))
10948  {
10949  continue; // move past the starting entry
10950  }
10951  if((Ptr->FormatType == Repeat) || Ptr >= TTDEPtr->ActionVector.end())
10952  {
10953  break;
10954  }
10955  if(Ptr->SignallerControl)
10956  {
10957  RetStr = "Train under signaller control";
10958  break;
10959  }
10960  if(Ptr->FormatType == TimeTimeLoc)
10961  {
10962  if(Ptr->ArrivalTime == Ptr->DepartureTime)
10963  {
10964  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(0, Ptr->ArrivalTime, RepNum, IncMins)) + ": Arrive & depart from " + Ptr->LocationName;
10965  }
10966  else
10967  {
10968  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(1, Ptr->ArrivalTime, RepNum, IncMins)) + ": Arrive at " + Ptr->LocationName + '\n' +
10969  Utilities->Format96HHMM(GetControllerTrainTime(2, Ptr->DepartureTime, RepNum, IncMins)) + ": Depart from " + Ptr->LocationName;
10970  Count++; // because there are 2 entries
10971  }
10972  }
10973  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
10974  {
10975  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(3, Ptr->ArrivalTime, RepNum, IncMins)) + ": Arrive at " + Ptr->LocationName;
10976  }
10977  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
10978  {
10979  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(4, Ptr->DepartureTime, RepNum, IncMins)) + ": Depart from " + Ptr->LocationName;
10980  }
10981  else if(Ptr->FormatType == PassTime) // new
10982  {
10983  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(5, Ptr->EventTime, RepNum, IncMins)) + ": Pass " + Ptr->LocationName;
10984  }
10985  else if(Ptr->Command == "Fns")
10986  {
10987  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(6, Ptr->EventTime, RepNum, IncMins)) + ": Form new service " +
10988  TrainController->GetRepeatHeadCode(46, Ptr->OtherHeadCode, RepNum, IncDig) + " at " + Ptr->LocationName;
10989  PartStr = ControllerGetNewServiceDepartureInfo(11, Ptr, RepNum, TTDEPtr, Ptr->LinkedTrainEntryPtr, IncMins, IncDig, PartStr); //if there is a next service this adds the new service departure time to PartStr
10990  }
10991  else if(Ptr->Command == "F-nshs")
10992  {
10993  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(7, Ptr->EventTime, RepNum, IncMins)) + ": Form new service " +
10994  Ptr->NonRepeatingShuttleLinkHeadCode + " at " + Ptr->LocationName;
10995  PartStr = ControllerGetNewServiceDepartureInfo(13, Ptr, 0, TTDEPtr, Ptr->LinkedTrainEntryPtr, IncMins, IncDig, PartStr); //if there is a next service this adds the new service departure time to RetStr
10996  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
10997  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
10998  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
10999  }
11000 //Since this is a new continuation entry service it can't be Fns-sh or Frh-sh but leave these in for consistency with TTrain::FloatingTimetableString
11001  else if((Ptr->Command == "Fns-sh") && (RepNum < (TTDEPtr->NumberOfTrains - 1))) // not the last repeat number
11002  {
11003  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(8, Ptr->EventTime, RepNum, IncMins)) + ": Form new service " +
11004  TrainController->GetRepeatHeadCode(47, Ptr->OtherHeadCode, RepNum + 1, IncDig) + " at " + Ptr->LocationName;
11005  // use RepNum+1 because it's the repeat number of the NEXT shuttle service that is relevant
11006  PartStr = ControllerGetNewServiceDepartureInfo(15, Ptr, RepNum + 1, TTDEPtr, Ptr->LinkedTrainEntryPtr, IncMins, IncDig, PartStr); //if there is a next service this adds the new service departure time to RetStr
11007  }
11008  else if((Ptr->Command == "Fns-sh") && (RepNum >= (TTDEPtr->NumberOfTrains - 1))) // last repeat number
11009  {
11010  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(9, Ptr->EventTime, RepNum, IncMins)) + ": Form new service " +
11011  Ptr->NonRepeatingShuttleLinkHeadCode, +" at " + Ptr->LocationName;
11012  PartStr = ControllerGetNewServiceDepartureInfo(17, Ptr, 0, TTDEPtr, Ptr->NonRepeatingShuttleLinkEntryPtr, IncMins, IncDig, PartStr); //if there is a next service this adds the new service departure time to RetStr
11013  }
11014  else if((Ptr->Command == "Frh-sh") && (RepNum < (TTDEPtr->NumberOfTrains - 1))) // not the last repeat number
11015  {
11016  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(10, Ptr->EventTime, RepNum, IncMins)) + ": Form new service " +
11017  TrainController->GetRepeatHeadCode(48, Ptr->OtherHeadCode, RepNum + 1, IncDig) + " at " + Ptr->LocationName;
11018  // use RepNum+1 because it's the repeat number of the NEXT shuttle service that is relevant
11019  PartStr = ControllerGetNewServiceDepartureInfo(19, Ptr, RepNum + 1, TTDEPtr, Ptr->LinkedTrainEntryPtr, IncMins, IncDig, PartStr); //if there is a next service this adds the new service departure time to RetStr
11020  }
11021  else if((Ptr->Command == "Frh-sh") && (RepNum >= (TTDEPtr->NumberOfTrains - 1))) // last repeat number
11022  {
11023  PartStr = "Terminate at " + Ptr->LocationName;
11024  }
11025  else if(Ptr->Command == "Frh")
11026  {
11027  PartStr = "Terminate at " + Ptr->LocationName;
11028  }
11029  else if(Ptr->Command == "Fer")
11030  {
11031  AnsiString AllowedExits;
11032  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(11, Ptr->EventTime, RepNum, IncMins)) + ": Exit railway" +
11033  TrainController->GetExitLocationAndAt(3, Ptr->ExitList, AllowedExits) + AllowedExits;
11034  }
11035  else if(Ptr->Command == "Fjo")
11036  {
11037  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(12, Ptr->EventTime, RepNum, IncMins)) + ": Join " + TrainController->GetRepeatHeadCode(49,
11038  Ptr->OtherHeadCode, RepNum, IncDig) + " at " + Ptr->LocationName;
11039  }
11040  else if(Ptr->Command == "jbo")
11041  {
11042  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(13, Ptr->EventTime, RepNum, IncMins)) + ": Joined by " + TrainController->GetRepeatHeadCode
11043  (50, Ptr->OtherHeadCode, RepNum, IncDig) + " at " + Ptr->LocationName;
11044  }
11045  else if(Ptr->Command == "fsp")
11046  {
11047  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(14, Ptr->EventTime, RepNum, IncMins)) + ": Front split to " +
11048  TrainController->GetRepeatHeadCode(51, Ptr->OtherHeadCode, RepNum, IncDig) + " at " + Ptr->LocationName;
11049  }
11050  else if(Ptr->Command == "rsp")
11051  {
11052  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(15, Ptr->EventTime, RepNum, IncMins)) + ": Rear split to " +
11053  TrainController->GetRepeatHeadCode(52, Ptr->OtherHeadCode, RepNum, IncDig) + " at " + Ptr->LocationName;
11054  }
11055  else if(Ptr->Command == "cdt")
11056  {
11057  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(16, Ptr->EventTime, RepNum, IncMins)) + ": Change direction at " + Ptr->LocationName;
11058  }
11059  else if(Ptr->Command == "dsc")
11060  {
11061  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(27, Ptr->EventTime, RepNum, IncMins)) + ": Change description at " + Ptr->LocationName;
11062  }
11063  else if(Ptr->Command == "cms")
11064  {
11065  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(32, Ptr->EventTime, RepNum, IncMins)) + ": Change maximum speed to " + Ptr->NewMaxSpeed + " at " + Ptr->LocationName;
11066  }
11067  if(RetStr != "")
11068  {
11069  RetStr = RetStr + '\n' + PartStr;
11070  }
11071  else
11072  {
11073  RetStr = PartStr;
11074  }
11075  Count++;
11076  }
11077  while(Ptr < TTDEPtr->ActionVector.end() && (Count < 33) && ((Ptr->Command == "") || ((Ptr->Command != "") && (Ptr->Command[1] != 'F'))));
11078  // limit of 33 allows a max of 34 entries (may have gone from 32 to 34 because of a TimeTimeLoc), which with track and train status gives
11079  // a max of 48 lines, at 13 pixels each, = 624 pixels & screen height has 641 so will fit comfortably. Also 34 timetable entries is as far
11080  // forward as anyone should wish to see without looking at the full timetable
11081  Utilities->CallLogPop(2072);
11082  return(RetStr);
11083 }
11084 
11085 // ---------------------------------------------------------------------------
11086 
11088  TTrainDataEntry *LinkedTrainDataPtr, int IncrementalMinutes, int IncrementalDigits, AnsiString RetStr)
11089 { //no delays as train not entered yet //above added IncrementalDigits at v2.18.0 for GetRepeatHeadCode
11090  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString(Ptr - TDEPtr->ActionVector.begin()) + ","
11091  + AnsiString(RptNum) + ",ControllerGetNewServiceDepartureInfo," + TDEPtr->HeadCode);
11092  AnsiString DepTime = "", EventTime = "";
11093  bool CDTFlag = false; //reports if train changes direction before departs
11094  TActionVector NewServiceAV = LinkedTrainDataPtr->ActionVector;
11095  AnsiString CurrentLocation = NewServiceAV.at(0).LocationName; //added at v2.12.0 to show departure direction
11096  AnsiString TowardsLocation = ""; //added at v2.12.0 to show departure direction
11097  for(TActionVectorIterator AVI = NewServiceAV.begin(); AVI < NewServiceAV.end(); AVI++) //added at v2.12.0 to obtain departure direction
11098  {
11099  if((AVI->LocationName != CurrentLocation) && (AVI->LocationName != "") && (TowardsLocation == ""))
11100  {
11101  TowardsLocation = AVI->LocationName;
11102  }
11103  else if((AVI->Command == "Fer") && (TowardsLocation == "") && !AVI->ExitList.empty())
11104  {
11105  TTrackElement TE = Track->TrackElementAt(1453, (AVI->ExitList.front()));
11106  if(TE.ActiveTrackElementName != "")
11107  {
11108  TowardsLocation = TE.ActiveTrackElementName;
11109  }
11110  else
11111  {
11112  TowardsLocation = AnsiString("track element ID ") + TE.ElementID;
11113  }
11114  }
11115  }
11116  for(TActionVectorIterator AVI = NewServiceAV.begin(); AVI < NewServiceAV.end(); AVI++)
11117  {
11118  if(AVI->Command == "cdt")
11119  {
11120  CDTFlag = !CDTFlag; //toggles flag - allows for there being more than one cdt before departure
11121  continue;
11122  }
11123  if((AVI->Command == "fsp") || (AVI->Command == "rsp"))
11124  {
11125  EventTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(21, AVI->EventTime, RptNum, IncrementalMinutes));
11126  RetStr += "\nNew service splits at " + EventTime;
11127  Utilities->CallLogPop(2237);
11128  return(RetStr);
11129  }
11130  if(AVI->Command == "jbo")
11131  {
11132  EventTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(30, AVI->EventTime, RptNum, IncrementalMinutes));
11133  RetStr += "\nNew service joined by " + GetRepeatHeadCode(70, AVI->OtherHeadCode, RptNum, IncrementalDigits) + " at " + EventTime;
11134  Utilities->CallLogPop(2238); //above added GetRepeatHeadCode at v2.18.0 (was just AVI->OtherHeadCode before)
11135  return(RetStr);
11136  }
11137  if((AVI->Command == "Fns") || (AVI->Command == "F-nshs") || (AVI->Command == "Fns-sh"))
11138  {
11139  EventTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(31, AVI->EventTime, RptNum, IncrementalMinutes));
11140  RetStr += "\nNew service finishes and forms another new service at " + EventTime;
11141  Utilities->CallLogPop(2607);
11142  return(RetStr);
11143  }
11144  if(AVI->Command == "Fjo")
11145  {
11146  EventTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(22, AVI->EventTime, RptNum, IncrementalMinutes));
11147  RetStr += "\nNew service finishes and joins " + TrainController->GetRepeatHeadCode(71, AVI->OtherHeadCode, RptNum, IncrementalDigits) + " at " + EventTime;
11148  Utilities->CallLogPop(2608); //above added GetRepeatHeadCode at v2.18.0 (was just AVI->OtherHeadCode before)
11149  return(RetStr);
11150  }
11151  if(AVI->Command == "Frh")
11152  {
11153  RetStr += "\nNew service finishes and remains at location.";
11154  Utilities->CallLogPop(2609);
11155  return(RetStr);
11156  }
11157  if((AVI->FormatType == TimeLoc) && (AVI->DepartureTime > TDateTime(-1))) //departure time set
11158  {
11159  DepTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(23, AVI->DepartureTime, RptNum, IncrementalMinutes));
11160  if(CDTFlag)
11161  {
11162  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
11163  {
11164  RetStr += "\nNew service changes direction then departs towards " + TowardsLocation + " at " + DepTime;
11165  }
11166  else
11167  {
11168  RetStr += "\nNew service changes direction then departs at " + DepTime;
11169  }
11170  }
11171  else
11172  {
11173  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
11174  {
11175  RetStr += "\nNew service departs towards " + TowardsLocation + " at " + DepTime;
11176  }
11177  else
11178  {
11179  RetStr += "\nNew service departs at " + DepTime;
11180  }
11181  }
11182  Utilities->CallLogPop(2239);
11183  return(RetStr);
11184  }
11185  }
11186  Utilities->CallLogPop(2223);
11187  return(RetStr);
11188 }
11189 
11190 // ---------------------------------------------------------------------------
11191 // $$$$$$$$$$$$$$$$$$$$$$ Start of Timetable Functions $$$$$$$$$$$$$$$$$$$$$$$$
11192 /*
11193  Note: The terms 'action' and 'entry' have been used freely for individual code lines within services in comments & in variable names, but
11194  for messages and in the manual and help files the term Entry is reserved for a complete service or train (i.e. an entry in the timetable),
11195  and 'event' is reserved for and individual code line within a service. Repeats use the term 'item' if they use any at all.
11196 
11197  In references to 'HeadCode' can have an optional prefix - up to 4 additional characters that can be anything, so long as last 4 digits
11198  represent the headcode. This allows links to be uniquely identified regardless of the headcode - so can have same headcodes as often as
11199  user wishes
11200 
11201  Prior to start time, anything except a line beginning with a time [...leading spaces...] HH:MM is ignored - can be
11202  descriptive text or anything user wishes
11203  A time on its own line [HH:MM], with or without leading spaces, but with anything following it before the CR (which will
11204  be ignored) is taken as the timetable start time.
11205  Thereafter there must be text on every line in the timetable, as the first blank line (or end of file) will be taken as the end of the
11206  timetable. Text can follow the 'end of timetable' blank line if the user wishes.
11207  A line within the timetable beginning with '*', with or without leading spaces, is ignored. Such lines can add text
11208  within the timetable if required.
11209  Timetable entries consist of one line per headcode (i.e. per service, not necessarily per train, as one train can run several different
11210  services)
11211  Each line starts with HeadCode & full train information for a new train (Snt or Snt-sh), or, for a continuing service
11212  (Sfs, Sns, Sns-sh or Sns-fsh), can have (a) Headcode only or (b) HeadCode + Description, nothing else
11213 
11214  All leading & trailing spaces before & after a line or any entry in a line are stripped off - these can be included to make reading a
11215  text timetable file easier
11216 
11217  form:-
11218  HeadCode[;Description (plain text, no commas or semicolons)][;StartSpeed(kph); MaxRunningSpeed(kph); Mass(tonnes, prog converts to kg);
11219  MaxBrakeRate(tonnes force, prog converts to m/s/s); & gross power(kW, prog converts to power at rail in w)
11220  then multiple entries, separated by commas, of the form:-
11221 
11222  HH:MM;Snt;RearStartIdent FrontStartIdent }StartNew }
11223  HH:MM;Snt-sh;RearStartIdent FrontStartIdent;Fsh HeadCode }SNTShuttle }
11224  HH:MM;Sns-sh;Fxx-sh HeadCode;F-nshs HeadCode (non-repeating)}SNSShuttle }
11225 
11226  HH:MM;Command;HeadCode (Sfs Sns jbo fsp rsp Fns Fjo Frh-sh) }TimeCmdHeadCode } Train action entries
11227  HH:MM;F-nshs;NonRepeatingShuttleLinkHeadCode }FNSNonRepeatToShuttle }
11228  HH:MM;Sns-fsh;NonRepeatingShuttleLinkHeadCode }SNSNonRepeatFromShuttle }
11229 
11230  HH:MM;Command (cdt) }TimeCmd }
11231  HH:MM;Command;Description (dsc) }TimeCmdDescription }
11232  HH:MM;Command;NewMaxSpeed (cms) }TimeCmdMaxSpeed }
11233  HH:MM;Location (arr & dep) }TimeLoc }
11234  HH:MM;HH:MM;Location }TimeTimeLoc }
11235  HH:MM;pas;Location }PassTime }
11236  HH:MM;Fns-sh;Snx-sh HeadCode;Sns-fsh HeadCode (non-rep) }FSHNewService }
11237  HH:MM;Fer;set of allowable IDs }ExitRailway }
11238  Command (Frh only) }FinRemHere }
11239 
11240  R;mm;dd;nn. Repeat Repeat entry
11241 
11242  Formats:
11243 
11244  Command only: Frh
11245  Time;Command: cdt
11246  Time;Command;Description dsc
11247  Time;Command;MaxSpeed msc
11248  Time;Command;Headcode: Sfs Sns jbo fsp rsp Fns Fjo Frh-sh F-nshs Sns-fsh
11249  Time;Command;2 Element IDs: Snt
11250  Time;Comand;n Element IDs: Fer
11251  Time;Command;rep Headcode;nonrep Headcode: Sns-sh Fns-sh
11252  Time;Command;2 Element IDs;Headcode Snt-sh
11253  Time;Command;Location pas
11254  Time;Location Arr Dep
11255  Time;Time;Location Arr & dep together
11256 
11257  11 Non-linked entries: Snt (located & unlocated); pas; cdt; dsc; msc; TimeLoc arr & dep; TimeTimeLoc; Fer; Frh
11258 
11259  9 1x Linked entries: Non-shuttle: fsp or rsp -> Sfs; Fns -> Sns; Fjo -> jbo; times must match, headcodes must match
11260  Shuttle: F-nshs -> Sns-sh: times match, F-nshs HeadCode matches Sns-sh 2nd Headcode;
11261  Fns-sh -> Sns-fsh: Fns-sh time + all repeats = Sns-fsh time, Fns-sh 2nd headcode matches Sns-fsh Headcode
11262 
11263  4 2x Linked entries, all shuttles:
11264 
11265  Frh-sh -> Snt-sh: Frh-sh time = Snt-sh time + 1 repeat while repeating, Frh-sh Headcode = Snt-sh Headcode;
11266  -> Sns-sh: Frh-sh time = Sns-sh time + 1 repeat while repeating, Frh-sh Headcode = Sns-sh 1st Headcode;
11267  -> Remain Here (at finish location after all repeats)
11268  Fns-sh -> Snt-sh: Frh-sh time = Snt-sh time + 1 repeat while repeating, Fns-sh 1st Headcode = Snt-sh Headcode
11269  -> Sns-sh: Frh-sh time = Sns-sh time + 1 repeat while repeating, Fns-sh 1st Headcode = Sns-sh 1st Headcode
11270 
11271  Moving/AtLoc states:-
11272 
11273  Successor state Type
11274 
11275  Snt located AtLoc ) Snt AtLoc successors: TimeLoc dep/jbo/fsp/rsp/cdt/dsc/msc/Frh/Fns/Fjo/Frh-sh/Fns-sh/F-nshs;
11276  Snt Unlocated Moving ) Snt Moving successors: TimeLoc arr/TimeTimeLoc/pas/Fer;
11277  Sfs AtLoc )
11278  Sns AtLoc ) Start
11279  Sns-fsh AtLoc )
11280  Snt-sh AtLoc )
11281  Sns-sh AtLoc )
11282 
11283  pas Moving )
11284  jbo AtLoc )
11285  fsp AtLoc )
11286  rsp AtLoc ) Intermediate
11287  cdt AtLoc )
11288  dsc AtLoc )
11289  msc AtLoc )
11290  TimeLoc arr Moving (bef), AtLoc (aft) )
11291  TimeLoc dep AtLoc (bef), Moving (aft) )
11292  TimeTimeLoc Moving )
11293 
11294  Fns Repeat/Nothing)
11295  Fjo Repeat/Nothing)
11296  Frh Repeat/Nothing)
11297  Fer Repeat/Nothing) Finish
11298  Frh-sh Repeat )
11299  Fns-sh Repeat )
11300  F-nshs Nothing )
11301 
11302  Descriptions:
11303  Snt New train
11304  Sfs New service from split
11305  Sns New service from another service
11306  Sns-fsh New non-repeating service from a shuttle service
11307  Snt-sh New shuttle train at a timetabled stop
11308  Sns-sh New shuttle service from a feeder service
11309 
11310  pas Pass
11311  jbo Be joined by another train
11312  fsp Front split
11313  rsp Rear split
11314  cdt Change direction of train
11315  dsc Change description of train
11316  msc Change max speed of train
11317  TimeLoc arr Arrival
11318  TimeLoc dep Departure
11319  TimeTimeLoc Arrival and departure
11320 
11321  Fns Finish & form a new service
11322  Fjo Finish & join another train
11323  Frh Finish & remain here
11324  Fer Finish & exit railway
11325  Frh-sh Finish & repeat shuttle, finally remain here
11326  Fns-sh Finish & repeat shuttle, finally form a non-repeating service
11327  F-nshs Finish & form a shuttle feeder service
11328 */
11329 
11330 bool TTrainController::TimetableIntegrityCheck(int Caller, char *FileName, bool GiveMessages, bool CheckLocationsExistInRailway) // true for success
11331 {
11332  // Error messages mainly given in called functions, five are given here - empty file; inability to find a start time; timetable containing
11333  // a line that is too long; timetable containing too few lines; and timetable failed to open.
11334  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TimetableIntegrityCheck," + AnsiString(FileName));
11335  // new for v0.2b
11336  // compile ActiveTrackElementNameMap
11337  TTrack::TActiveTrackElementNameMapEntry ActiveTrackElementNameMapEntry;
11338 
11340  for(unsigned int x = 0; x < Track->TrackVector.size(); x++)
11341  {
11342  // if((Track->TrackElementAt(, x).ActiveTrackElementName != "") && (Track->TrackElementAt(, x).TrackType != Continuation))
11344  == Track->ContinuationNameMap.end())
11345  {
11346  // exclude any name that appears in a continuation, error message given in tt validation if try to include such a name in a tt
11347  ActiveTrackElementNameMapEntry.first = Track->TrackElementAt(1035, x).ActiveTrackElementName;
11348  ActiveTrackElementNameMapEntry.second = 0; // this is a dummy value
11349  Track->ActiveTrackElementNameMap.insert(ActiveTrackElementNameMapEntry);
11350  }
11351  }
11353  // end of new section
11354  std::ifstream TTBLFile(FileName, std::ios_base::binary);
11355 
11356  // binary mode so the "\r\n" pairs stay as they are rather than being entered as '\n'
11357  if(TTBLFile.is_open())
11358  {
11359  char *TrainTimetableString = new char[10000];
11360  // enough for over 200 stations, should be adequate!
11361  bool EndOfFile = false;
11362  int Count = 0;
11363  // counts 'relevant' lines, i.e ignores any before the start time on its own line
11364  TTBLFile.getline(TrainTimetableString, 10000, '\0');
11365  // delimiter is '\0' as it's an AnsiString
11366  if(TTBLFile.eof() && (TrainTimetableString[0] == '\0'))
11367  // file empty - stores a null in 1st position if doesn't load any characters
11368  {
11369  // may still have eof even if read a line (no CRLF at end), and
11370  // if so need to process it
11371  TimetableMessage(GiveMessages, "Timetable invalid - file empty");
11372  TTBLFile.close();
11373  delete[] TrainTimetableString;
11374  Utilities->CallLogPop(1611);
11375  return(false);
11376  }
11377  AnsiString OneLine(TrainTimetableString);
11378  bool FinalCallFalse = false;
11379  while((Count == 0) && !ProcessOneTimetableLine(5, Count, OneLine, EndOfFile, FinalCallFalse, GiveMessages, CheckLocationsExistInRailway))
11380  // get rid of lines before the start time
11381  {
11382  // ProcessOneTimetableLine returns true for a valid start time, an EndOfFile &/or a blank entry
11383  TTBLFile.getline(TrainTimetableString, 10000, '\0');
11384  if(TTBLFile.eof() && (TrainTimetableString[0] == '\0'))
11385  // stores a null in 1st position if doesn't load any characters
11386  {
11387  // may still have eof even if read a line (no CRLF at end), and
11388  // if so need to process it
11389  TimetableMessage(GiveMessages, "Timetable invalid - unable to find a valid start time on its own line");
11390  TTBLFile.close();
11391  delete[] TrainTimetableString;
11392  Utilities->CallLogPop(772);
11393  return(false);
11394  }
11395  OneLine = AnsiString(TrainTimetableString);
11396  }
11397  // here when have accepted the start time
11398  Count++; // increment past the start time
11399  while(!EndOfFile)
11400  {
11401  TTBLFile.getline(TrainTimetableString, 10000, '\0');
11402  // get next line after start time
11403  if(TTBLFile.eof() && (TrainTimetableString[0] == '\0'))
11404  // stores a null in 1st position if doesn't load any characters
11405  {
11406  // may still have eof even if read a line (no CRLF at end), and
11407  // if so need to process it
11408  EndOfFile = true;
11409  OneLine = "";
11410  }
11411  else
11412  {
11413  OneLine = AnsiString(TrainTimetableString);
11414  }
11415  if(OneLine.Length() > 9999)
11416  {
11417  TimetableMessage(GiveMessages, "Timetable contains a line that is too long - 10,000 or more characters!");
11418  TTBLFile.close();
11419  delete[] TrainTimetableString;
11420  Utilities->CallLogPop(789);
11421  return(false);
11422  }
11423  bool FinalCallFalse = false;
11424  if(!ProcessOneTimetableLine(6, Count, OneLine, EndOfFile, FinalCallFalse, GiveMessages, CheckLocationsExistInRailway))
11425  // false for FinalCall - just checking at this stage
11426  {
11427  TTBLFile.close();
11428  delete[] TrainTimetableString;
11429  Utilities->CallLogPop(770);
11430  return(false);
11431  }
11432  if(EndOfFile && (Count < 2))
11433  // Timetable must contain at least two relevant lines, one for start time and at least one train
11434  {
11435  TimetableMessage(GiveMessages, "Timetable has too few or no relevant entries - must have a start time on its own line and at least one train");
11436  TTBLFile.close();
11437  delete[] TrainTimetableString;
11438  Utilities->CallLogPop(771);
11439  return(false);
11440  }
11441  Count++;
11442  }
11443  delete[] TrainTimetableString;
11444  TTBLFile.close();
11445  } // if(TTBLFile.is_open())
11446  else
11447  {
11448  TimetableMessage(GiveMessages, "Failed to open timetable file, make sure it's spelled correctly, it exists and isn't open in another application");
11449  Utilities->CallLogPop(2154);
11450  return(false);
11451  }
11452  Utilities->CallLogPop(753);
11453  return(true);
11454 }
11455 
11456 // ---------------------------------------------------------------------------
11457 
11458 bool TTrainController::ProcessOneTimetableLine(int Caller, int Count, AnsiString OneLine, bool &EndOfFile, bool FinalCall, bool GiveMessages,
11459  bool CheckLocationsExistInRailway) // return true for success
11460 
11461 /* Format:
11462  Prior to start time, anything except a line beginning with a time [...leading spaces...] HH:MM is ignored - can be
11463  descriptive text or anything user wishes
11464  A time on its own line [HH:MM], with or without leading spaces, but with anything following it before the CR (which will
11465  be ignored) is taken as the timetable start time.
11466  Thereafter there must be text on every line in the timetable, as the first blank line (or end of file) will be taken as the end of the
11467  timetable. Text can follow the 'end of timetable' blank line if the user wishes.
11468  A line within the timetable beginning with '*', with or without leading spaces, is ignored. Such lines can add text
11469  within the timetable if required.
11470  Timetable entries consist of one line per headcode (i.e. per service, not necessarily per train, as one train can run several different
11471  services)
11472  Each line starts with HeadCode & full train information for a new train (Snt or Snt-sh), or, for a continuing service
11473  (Sfs, Sns, Sns-sh or Sns-fsh), can have (a) Headcode only or (b) HeadCode + Description, nothing else
11474 
11475  All leading & trailing spaces before & after a line or any entry in a line are stripped off - these can be included to make reading a
11476  text timetable file easier
11477 
11478  form:-
11479  HeadCode[;Description (plain text, no commas or semicolons)][;StartSpeed(kph); MaxRunningSpeed(kph); Mass(tonnes, prog converts to kg);
11480  MaxBrakeRate(tonnes force, prog converts to m/s/s); & gross power(kW, prog converts to power at rail in w)
11481  then multiple entries, separated by commas, of the form:-
11482 
11483  Format FormatType
11484  [W]HH:MM;Command (cdt) }TimeCmd }
11485  [W]HH:MM;dsc;new description }TimeCmdDescription }
11486  [W]HH:MM;cms;new max speed }TimeCmdMaxSpeed }
11487  [W]HH:MM;Fer;set of allowable IDs }ExitRailway }
11488  [W]HH:MM;pas;Location }PassTime }
11489  [W]HH:MM;Snt;RearStartIdent FrontStartIdent }StartNew }
11490  [W]HH:MM;Command;HeadCode (Sfs Sns jbo fsp rsp Fns Fjo Frh-sh) }TimeCmdHeadCode }
11491  [W]HH:MM;F-nshs;non-repeating headcode }FNSNonRepeatToShuttle }
11492  [W]HH:MM;Sns-fsh;NonRepeatingShuttleLinkHeadCode }SNSNonRepeatFromShuttle } Train action entries
11493  [W]HH:MM;Snt-sh;RearStartIdent FrontStartIdent;FSH HeadCode }SNTShuttle }
11494  [W]HH:MM;Sns-sh;FSH HeadCode;F-nshs HeadCode (non-repeating) }SNSShuttle }
11495  [W]HH:MM;Fns-sh;Details }FSHNewService }
11496  [W]HH:MM;Location (arr & dep) }TimeLoc }
11497  [W]HH:MM;HH:MM;Location }TimeTimeLoc }
11498  Command (Frh only) }FinRemHere }
11499 
11500  R;mm;dd;nn. Repeat Repeat entry
11501 
11502  Two times represent arrival & departure, without any other events between (if arrival and departure times are the same
11503  then departure is 30 sec after arrival), single time represents (a) event time; (b) arrival time if train not already
11504  at location; or (c) departure time if train already at location (including train started at location either as a new
11505  train or as a continuation service train at that location). All lines must contain a start entry and a finish entry,
11506  the finish being the last unless there is a repeat entry. The repeat entry begins with 'R', then the incremental
11507  minutes, incremental train headcode last 2 digits, and number of repeats.
11508 
11509  Shuttle entries are where can loop back to an earlier Snt-sh or Sns-sh entry from a Frh-sh or Fns-sh (Finish Shuttle)
11510  entry. Here the shuttle start can have two entries, one from a set position (Snt-sh, must be located) or from a F-nshs
11511  (Sns-sh) - with NO repeat from this source, and from a Fxx-sh, with repeats. After all shuttle repeats Frh-sh remains
11512  where it is, and Fns-sh links to a new service (via an Sns entry), but there must be no repeats in this new service
11513  (it's for a shuttle train to return to depot at end of services)
11514 
11515  Command/Location & details are as follows:-
11516 
11517  Although headcodes can be duplicated, all joins, splits, new services etc give other headcode from both trains' povs, and
11518  these have to match once only, i.e. if 2E44 splits to 2E45 then it can't split to 2E45 anywhere else, and 2E45 must give
11519  2E44 in its Sfs entry. All these are checked.
11520  ***add note re shuttles & their use of otherheadcodes + non-repeating headcodes***
11521 
11522  Start commands:-
11523  Snt (StartNew) = Start New Train, i.e. create new train, details = rearstartident, space, frontstartident (can't confuse
11524  with loc as a start entry can't have a location as details)
11525  Sfs (TimeCmdHeadCode) = Start From Split, create a new train that has split from another train (& listed in other train's
11526  timetable line), details = other headcode - (can't confuse with loc as start can't be a loc)
11527  Sns (TimeCmdHeadCode) = Start, headcode change from earlier service - no need to create train as already exists, it just
11528  changes its relevant information, details = old headcode (can't confuse with loc as start can't be a loc)
11529  Snt-sh (SNTShuttle) = Start New Train, i.e. create new train, details = rearstartident, space, frontstartident (can't
11530  confuse with loc as start can't be a loc) then the Fsh-XX service headcode (OtherHeadCode can't be same headcode)
11531  Sns-sh (SNSShuttle) = Start, headcode change from earlier service - no need to create train as already exists, it just
11532  changes its relevant information, details = the FSH-XX service headcode (OtherHeadCode, can't be same headcode)
11533  followed by the non-repeating F-nshs headcode (NonRepeatingShuttleLinkHeadCode)
11534  Sns-fsh (SNSNonRepeatFromShuttle) = Start as a non-repeating service from a shuttle service that has finished all its
11535  repeats, details = NonRepeatingShuttleLinkHeadCode for the corresponding shuttle Fns-sh service
11536 
11537  Intermediate commands:-
11538  Time - Location (TimeLoc), can be arrival or departure depending on context
11539  Time Time location (TimeTimeLoc), arrival and departure
11540  Location Name (exactly as used in the railway) in TimeLoc & TimeTimeLoc means that the train is required to stop at the location
11541  pas (PassTime), Time;pas;Location
11542  jbo (TimeCmdHeadCode) = Joined By Other = joined by other train, details = new headcode (await other train - may be delayed). Note that the
11543  joining train's finish details must correspond or the file check will fail
11544  fsp (TimeCmdHeadCode) = Front Split = a new train splits away from front of this train, both trains in same direction, details = new headcode (create
11545  new train - that train's starting information must correspond)
11546  rsp (TimeCmdHeadCode) = Rear Split = a new train splits away from rear of this train, both trains in same direction, details = new headcode (create
11547  new train - that train's starting information must correspond)
11548  cdt (TimeCmd) = Change Direction of Train = change direction, no details needed & no train creation
11549  dsc (TimeCmdDescription) = Change description of train = new description only, no train creation
11550  cms (TimeCmdMaxSpeed) = Change maximum speed of train = new max speed only, no train creation
11551 
11552  Finish commands:-
11553  Fns (TimeCmdHeadCode) = Finish New Service = finish, form new service in same direction, details = new headcode (no train
11554  creation)
11555  F-nshs (FNSShuttle) = Finish New Service (Shuttle) = finish, form new shuttle service in same direction, details =
11556  shuttle headcode (no train creation)
11557  Fjo (TimeCmdHeadCode) = Finish Join Other = finish, join other train (which must be on an adjacent element, either end -
11558  may have to wait for it), details = new headcode (delete train)
11559  Frh (FinRemHere) = Finish Remain Here = stay here indefinitely, no details & no time needed
11560  Fer (ExitRailway) = Finish, exit railway (i.e at a continuation) - details = set of allowable exit IDs
11561  Frh-sh (TimeCmdHeadCode) = Finish then restart as a shuttle using Snt-sh or Sns-sh, when all shuttle repeats done remain
11562  here
11563  Fns-sh (FSHNewService) = Finish then restart as a shuttle using Snt-sh or Sns-sh, when all shuttle repeats done form new
11564  service via Sns-fsh using the NonRepeatingShuttleLinkHeadCode
11565 
11566  Repeat:-
11567  R;mm;dd;nn (Repeat) where mm = minute increment, dd = 2nd 2 headcode digit increment & nn = no. of repeats (no max as can duplicate
11568  headcodes - it is up to user to avoid duplicates if he/she wishes to.
11569 
11570  Checks carried out with error messages in this function:-
11571  At least one comma in a service line (it's based on a .csv file)
11572  No entries following train information;
11573  At least one comma in remainder after train information (i.e at least a start and a finish entry);
11574  SplitEntry returns false in an intermediate entry - message repeats the entry for information;
11575  First entry not a start entry;
11576  Train information incomplete before a start entry;
11577  Entry follows a finish entry but doesn't begin with 'R';
11578  SplitEntry returns false in a finish entry - message repeats the entry for information;
11579  Last action entry isn't a finish entry.
11580 
11581  Function returns false with no message if:-
11582  Timetable start time invalid (no message is given for an invalid time as the line is assumed to be an irrelevant line; if no start
11583  time is found at all then an error message is given in the calling function);
11584  SplitTrainInfo returns false (message given in called function);
11585  SplitRepeat returns false (message given in called function).
11586 */
11587 {
11588  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ProcessOneTimetableLine," + AnsiString(Count) + "," + OneLine + "," +
11589  AnsiString((short)FinalCall) + "," + AnsiString((short)CheckLocationsExistInRailway));
11590  TTrainDataEntry TempTrainDataEntry;
11591 
11592  EndOfFile = false;
11593  StripSpaces(0, OneLine);
11594  // strip both leading and trailing spaces at ends of line and spaces before and after all commas and
11595  // semicolons within the line
11596  ServiceReference = "";
11597  if(OneLine != "")
11598  {
11599  if(OneLine[1] != '*')
11600  {
11601  int DelimPos = OneLine.Pos(';');
11602  int CPos = OneLine.Pos(','); //added at v2.21.0 as Sns entries without a description have comma instead of semicolon but service ref. still valid
11603  if((CPos > 0) && (CPos < DelimPos))
11604  {
11605  DelimPos = CPos;
11606  }
11607 
11608  if(DelimPos == 0)
11609  {
11610  ServiceReference = OneLine.SubString(1, 8);
11611  }
11612  else
11613  {
11614  ServiceReference = OneLine.SubString(1, (DelimPos - 1));
11615  }
11616  }
11617  }
11618  bool AllCommas = true;
11619 
11620  for(int x = 1; x < OneLine.Length() + 1; x++) // check for nothing but commas (may be all commas if created from Excel) or a blank line
11621  {
11622  if(OneLine[x] != ',')
11623  {
11624  AllCommas = false;
11625  }
11626  }
11627  if(AllCommas || (OneLine == ""))
11628  {
11629  if(Count > 0)
11630  {
11631  EndOfFile = true;
11632  // returns true for a blank line (or a line of all commas) - treated as end of file
11633  Utilities->CallLogPop(1018);
11634  return(true);
11635  }
11636  else // count == 0 so not yet found a start time, no message to be given
11637  {
11638  Utilities->CallLogPop(754);
11639  return(false);
11640  }
11641  }
11642  AnsiString First = "", Second = "", Third = "", Fourth = "";
11643  int RearStartOrRepeatMins = 0, FrontStartOrRepeatDigits = 0, NumberOfRepeats = 0;
11644  TDateTime EventTime(0), ArrivalTime(0), DepartureTime(0);
11645  TDateTime StartTime(0);
11646  TNumList ExitList;
11647  bool Warning = false;
11648 
11649  if(Count == 0) // no start time found yet
11650  {
11651 /* dropped at v0.6b
11652  AnyHeadCodeValid = false;
11653  if(OneLine.SubString(6,5) == ";0000")
11654  {
11655  AnyHeadCodeValid = true;
11656  }
11657 */
11658  if(!CheckTimeValidity(0, OneLine, StartTime))
11659  {
11660  // no message is given for an invalid time as it's assumed to be an irrelevant line
11661  // if no start time is found at all then an error message is given in the calling function
11662  // AnyHeadCodeValid = false;
11663  Utilities->CallLogPop(755);
11664  return(false);
11665  }
11666  if(FinalCall) // here if start time valid
11667  {
11668  TTClockTime = StartTime;
11669  TimetableStartTime = StartTime;
11670  }
11671  }
11672  else
11673  {
11674  AnsiString TrainInfoStr = "", HeadCode = "", Description = "";
11675  int StartSpeed = 0, MaxRunningSpeed = 0, Mass = 0;
11676  double MaxBrakeRate = 0;
11677  double PowerAtRail = 0;
11678  int SignallerSpeed = 0;
11679  if(OneLine[1] == '*')
11680  {
11681  Utilities->CallLogPop(1581);
11682  return(true);
11683  // ignore any line beginning with '*' but return true as there is no error
11684  }
11685  int Pos = OneLine.Pos(',');
11686  if(Pos == 0)
11687  {
11688  int SubStringLength = 20;
11689  if(OneLine.Length() < 20)
11690  {
11691  SubStringLength = OneLine.Length();
11692  }
11693  TimetableMessage(GiveMessages, "Error in timetable - entry incomplete: see '" + OneLine.SubString(1, SubStringLength) + "'....");
11694  Utilities->CallLogPop(766);
11695  return(false);
11696  }
11697  TrainInfoStr = OneLine.SubString(1, Pos - 1);
11698  if(!SplitTrainInfo(0, TrainInfoStr, HeadCode, Description, StartSpeed, MaxRunningSpeed, Mass, MaxBrakeRate, PowerAtRail, SignallerSpeed,
11699  GiveMessages)) // error messages given in SplitTrainInfo
11700  {
11701  Utilities->CallLogPop(773);
11702  return(false);
11703  }
11704  if(FinalCall)
11705  {
11706  // store Train info - conversions done in SplitTrainInfo
11707  // only headcode mandatory for continuing services
11708  //HeadCode = ServiceReference until final section of SecondPassActions
11709  TempTrainDataEntry.HeadCode = HeadCode;
11710  TempTrainDataEntry.ServiceReference = HeadCode;
11711  TempTrainDataEntry.FixedDescription = Description; //name changed at v2.16.1
11712  TempTrainDataEntry.ExplicitDescription = false;
11713  if(Description != "")
11714  {
11715  TempTrainDataEntry.ExplicitDescription = true;
11716  }
11717  TempTrainDataEntry.StartSpeed = StartSpeed;
11718  TempTrainDataEntry.Mass = Mass;
11719  TempTrainDataEntry.MaxRunningSpeed = MaxRunningSpeed;
11720  TempTrainDataEntry.MaxBrakeRate = MaxBrakeRate;
11721  TempTrainDataEntry.PowerAtRail = PowerAtRail;
11722  TempTrainDataEntry.SignallerSpeed = SignallerSpeed;
11723  TTrainOperatingData TempTrainOperatingData;
11724  TempTrainDataEntry.TrainOperatingDataVector.push_back(TempTrainOperatingData); // push empty vector for now
11725  }
11726  AnsiString NewRemainder = OneLine.SubString(Pos + 1, OneLine.Length() - Pos);
11727  // now left with series of entries for this train, but there may be a string of commas at the end of the line if created by Excel
11728  // so strip them off
11729  while(NewRemainder[NewRemainder.Length()] == ',')
11730  {
11731  if(NewRemainder.Length() > 1)
11732  {
11733  NewRemainder = NewRemainder.SubString(1, NewRemainder.Length() - 1);
11734  }
11735  else
11736  {
11737  NewRemainder = "";
11738  break;
11739  }
11740  }
11741  // check if zero length & fail if so
11742  if(NewRemainder == "")
11743  {
11744  TimetableMessage(GiveMessages, "Error in timetable - no events following train: '" + OneLine + "'");
11745  Utilities->CallLogPop(769);
11746  return(false);
11747  }
11748  // now have one more entry than there are commas
11749  int CommaCount = 0;
11750  for(int x = 1; x < NewRemainder.Length() + 1; x++)
11751  {
11752  if(NewRemainder[x] == ',')
11753  {
11754  CommaCount++;
11755  }
11756  } // must have at least 1 comma, for start & finish entries, unless train is entered under signaller control
11757  if(CommaCount == 0)
11758  {
11759  if((NewRemainder.SubString(7, 3) != "Snt") || (NewRemainder[NewRemainder.Length()] != 'S'))
11760  {
11761  int SubStringLength = 20;
11762  if(OneLine.Length() < 20)
11763  {
11764  SubStringLength = OneLine.Length();
11765  }
11766  TimetableMessage(GiveMessages,
11767  "Error in timetable - must have at least a start and a finish event for a train that is not started under signaller control - see line beginning: '" +
11768  OneLine.SubString(1, SubStringLength) + "'....");
11769  Utilities->CallLogPop(783);
11770  return(false);
11771  }
11772  }
11773  AnsiString OneEntry = "";
11774  TTimetableFormatType FormatType;
11775  TTimetableSequenceType SequenceType;
11776  TTimetableLocationType LocationType;
11777  TTimetableShuttleLinkType ShuttleLinkType;
11778  bool FinishFlag = false;
11779 // bool NewTrain = false;//added at v2.14.0 to record created trains for later zero power checks <-- not needed after zero power restriction dropped
11780  for(int x = 0; x < CommaCount + 1; x++)
11781  {
11782  if((CommaCount == 0) || (x < CommaCount))
11783  // i.e. train entered under signaller control with no repeats, or entry is not the last,
11784  // in which case there's a comma & finish element or repeat still to come this entry could
11785  // be a finish but can't be a repeat
11786  {
11787  if(CommaCount == 0)
11788  {
11789  OneEntry = NewRemainder;
11790  NewRemainder = "";
11791  }
11792  else
11793  {
11794  Pos = NewRemainder.Pos(',');
11795  OneEntry = NewRemainder.SubString(1, Pos - 1);
11796  NewRemainder = NewRemainder.SubString(Pos + 1, NewRemainder.Length() - Pos);
11797  }
11798  First = "";
11799  Second = "";
11800  Third = "";
11801  Fourth = "";
11802  RearStartOrRepeatMins = 0;
11803  FrontStartOrRepeatDigits = 0;
11804  NumberOfRepeats = 0;
11805  if(!SplitEntry(0, OneEntry, GiveMessages, CheckLocationsExistInRailway, First, Second, Third, Fourth, RearStartOrRepeatMins,
11806  FrontStartOrRepeatDigits, FormatType, LocationType, SequenceType, ShuttleLinkType, ExitList, Warning))
11807  {
11808  TimetableMessage(GiveMessages, "Error in timetable - Event: '" + OneEntry + "'");
11809  Utilities->CallLogPop(756);
11810  return(false);
11811  }
11812 /* not needed at v2.19.0
11813  if((Second == "Snt") || (Second == "Snt-sh")) //added at v2.14.0, see above
11814  {
11815  NewTrain = true; not needed when zero power restrictions removed
11816  }
11817 */
11818  // check if warning for Frh or Fjo & reject
11819  if(Warning && (Second == "Frh"))
11820  {
11821  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry + "': warnings cannot be given for 'Frh' events");
11822  Utilities->CallLogPop(1793);
11823  return(false);
11824  }
11825  if(Warning && (Second == "Fjo"))
11826  {
11827  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry +
11828  "': warnings cannot be given for 'Fjo' events, for a train join warning add a 'W' prefix to the 'jbo' event");
11829  Utilities->CallLogPop(1794);
11830  return(false);
11831  }
11832  //below added at v2.14.0 to prevent unpowered trains attempting to be joined by (Second == jbo), split (Second -- fsp or rsp),
11833  //or change direction. Form a new service dealt with below for zero power as it's a finish event. <-- removed at v2.19.0
11834 
11835 /* restriction removed at v2.19.0
11836  if(NewTrain && (PowerAtRail < 1) && (Second == "jbo"))
11837  {
11838  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry +
11839  "': a train created without power can't 'be joined by' another train (i.e. can't include command 'jbo'), "
11840  "use command 'Fjo' (i.e. 'join' another train) instead immediately after the line containing 'Snt', and use "
11841  "command 'jbo' for the train it is to join.");
11842  Utilities->CallLogPop(2545);
11843  return(false);
11844  }
11845  if(NewTrain && (PowerAtRail < 1) && ((Second == "fsp") || (Second == "rsp")))
11846  {
11847  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry +
11848  "': a train created without power can't split.");
11849  Utilities->CallLogPop(2546);
11850  return(false);
11851  }
11852  if(NewTrain && (PowerAtRail < 1) && (Second == "cdt"))
11853  {
11854  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry +
11855  "': a train created without power can't change direction under timetable control.");
11856  Utilities->CallLogPop(2547);
11857  return(false);
11858  }
11859 */
11860  //end of new additions
11861  if(x == 0) // should be start event
11862  {
11863  if(SequenceType != StartSequence)
11864  {
11865  TimetableMessage(GiveMessages, "Error in timetable - First event not a start event: '" + OneEntry + "'");
11866  Utilities->CallLogPop(784);
11867  return(false);
11868  }
11869  if((Second == "Snt") && (Fourth == 'S') && (NewRemainder != ""))
11870  {
11871  if(NewRemainder[1] != 'R')
11872  {
11873  TimetableMessage(GiveMessages,
11874  "Error in timetable - the only event that can follow a train created under signaller control is a repeat, see '" +
11875  OneEntry + "'");
11876  Utilities->CallLogPop(787);
11877  return(false);
11878  }
11879  }
11880  if((Second == "Snt") || (Second == "Snt-sh"))
11881  // need full train information including non-default values for at least HeadCode, Description,
11882  // MaxRunningSpeed, Mass, MaxBrakeRate, & PowerAtRail
11883  {
11884  if((HeadCode == "") || (Description == "") || (MaxRunningSpeed == 0) || (Mass == 0) || (MaxBrakeRate == 0)) // ||
11885  // (PowerAtRail == 0)) allowed 0 for power at v2.4.0
11886  {
11887  TimetableMessage(GiveMessages, "Error in timetable - train information incomplete before 'Snt' or 'Snt-sh' start event: '" +
11888  OneEntry + "'");
11889  Utilities->CallLogPop(1783);
11890  return(false);
11891  }
11892  }
11893  if((Second == "Sfs") || (Second == "Sns") || (Second == "Sns-sh") || (Second == "Sns-fsh"))
11894  // service continuation - need at least non-default value for HeadCode
11895  {
11896  if(HeadCode == "")
11897  {
11898  TimetableMessage(GiveMessages, "Error in timetable - headcode missing before 'Sfs', 'Sns', 'Sns-sh' or 'Sns-fsh' start event: '" +
11899  OneEntry + "'");
11900  Utilities->CallLogPop(788);
11901  return(false);
11902  }
11903  if((StartSpeed != 0) || (MaxRunningSpeed != 0) || (Mass != 0) || (MaxBrakeRate != 0) || (PowerAtRail != 0))
11904  {
11905  TimetableMessage(GiveMessages,
11906  "Error in timetable - information additional to a headcode & optional description given before 'Sfs', 'Sns', 'Sns-sh' or 'Sns-fsh' start event: '" +
11907  OneEntry + "'");
11908  Utilities->CallLogPop(843);
11909  return(false);
11910  }
11911  }
11912  }
11913  if(SequenceType == FinishSequence)
11914  {
11915  FinishFlag = true;
11916  // marker for only permitted additional entry being a repeat, only needed if the
11917  // finish entry is not the last entry
11918  }
11919  if(FinalCall)
11920  {
11921  // interpret & add to ActionVector
11922  TDateTime TempTime;
11923  TActionVectorEntry ActionVectorEntry;
11924  ActionVectorEntry.FormatType = FormatType;
11925  ActionVectorEntry.LocationType = LocationType;
11926  ActionVectorEntry.SequenceType = SequenceType;
11927  ActionVectorEntry.ShuttleLinkType = ShuttleLinkType;
11928  ActionVectorEntry.Warning = Warning;
11929  if(FormatType == TimeLoc)
11930  {
11931  if(CheckTimeValidity(1, First, ActionVectorEntry.EventTime))
11932  {
11933  ;
11934  } // these will all be true as final call
11935 
11936  ActionVectorEntry.LocationName = Second;
11937  }
11938  else if(FormatType == PassTime) // new
11939  {
11940  if(CheckTimeValidity(17, First, ActionVectorEntry.EventTime))
11941  {
11942  ;
11943  }
11944  ActionVectorEntry.Command = Second;
11945  ActionVectorEntry.LocationName = Third;
11946  }
11947  else if(FormatType == TimeTimeLoc)
11948  {
11949  if(CheckTimeValidity(2, First, ActionVectorEntry.ArrivalTime))
11950  {
11951  ;
11952  }
11953  if(CheckTimeValidity(3, Second, ActionVectorEntry.DepartureTime))
11954  {
11955  ;
11956  }
11957  ActionVectorEntry.LocationName = Third;
11958  }
11959  else if(FormatType == TimeCmd)
11960  {
11961  if(CheckTimeValidity(4, First, ActionVectorEntry.EventTime))
11962  {
11963  ;
11964  }
11965  ActionVectorEntry.Command = Second;
11966  }
11967  else if(FormatType == ExitRailway)
11968  {
11969  if(CheckTimeValidity(18, First, ActionVectorEntry.EventTime))
11970  {
11971  ;
11972  }
11973  ActionVectorEntry.Command = Second;
11974  ActionVectorEntry.ExitList = ExitList;
11975  }
11976  else if(FormatType == StartNew)
11977  {
11978  if(CheckTimeValidity(5, First, ActionVectorEntry.EventTime))
11979  {
11980  ;
11981  }
11982  ActionVectorEntry.Command = Second;
11983  ActionVectorEntry.RearStartOrRepeatMins = RearStartOrRepeatMins;
11984  ActionVectorEntry.FrontStartOrRepeatDigits = FrontStartOrRepeatDigits;
11985  if(Fourth == 'S')
11986  {
11987  ActionVectorEntry.SignallerControl = true;
11988  }
11989  }
11990  else if(FormatType == SNTShuttle)
11991  {
11992  if(CheckTimeValidity(6, First, ActionVectorEntry.EventTime))
11993  {
11994  ;
11995  }
11996  ActionVectorEntry.Command = Second;
11997  ActionVectorEntry.RearStartOrRepeatMins = RearStartOrRepeatMins;
11998  ActionVectorEntry.FrontStartOrRepeatDigits = FrontStartOrRepeatDigits;
11999  ActionVectorEntry.OtherHeadCode = Fourth;
12000  }
12001  else if(FormatType == SNSShuttle)
12002  {
12003  if(CheckTimeValidity(7, First, ActionVectorEntry.EventTime))
12004  {
12005  ;
12006  }
12007  ActionVectorEntry.Command = Second;
12008  ActionVectorEntry.OtherHeadCode = Third;
12009  ActionVectorEntry.NonRepeatingShuttleLinkHeadCode = Fourth;
12010  }
12011  else if(FormatType == TimeCmdHeadCode) //fsp/rsp is TimeCmdHeadCode but may have a Fourth - see below
12012  {
12013  if(CheckTimeValidity(8, First, ActionVectorEntry.EventTime))
12014  {
12015  ;
12016  }
12017  ActionVectorEntry.Command = Second;
12018  ActionVectorEntry.OtherHeadCode = Third;
12019  if((Second == "fsp") || (Second == "rsp")) //new at v2.15.0 & checked in SplitEntry
12020  {
12021  ActionVectorEntry.SplitDistribution = "50-50"; //added at v2.18.0
12022  if(Fourth != "")
12023  {
12024  ActionVectorEntry.SplitDistribution = Fourth;
12025  }
12026  }
12027  }
12028  else if((FormatType == FNSNonRepeatToShuttle) || (FormatType == SNSNonRepeatFromShuttle))
12029  {
12030  if(CheckTimeValidity(9, First, ActionVectorEntry.EventTime))
12031  {
12032  ;
12033  }
12034  ActionVectorEntry.Command = Second;
12035  ActionVectorEntry.NonRepeatingShuttleLinkHeadCode = Third;
12036  }
12037  else if(FormatType == FSHNewService)
12038  {
12039  if(CheckTimeValidity(10, First, ActionVectorEntry.EventTime))
12040  {
12041  ;
12042  }
12043  ActionVectorEntry.Command = Second;
12044  ActionVectorEntry.OtherHeadCode = Third;
12045  ActionVectorEntry.NonRepeatingShuttleLinkHeadCode = Fourth;
12046  }
12047  else if(FormatType == FinRemHere)
12048  {
12049  ActionVectorEntry.Command = Second;
12050  }
12051  else if(FormatType == TimeCmdDescription) //new at v2.15.0
12052  {
12053  if(CheckTimeValidity(35, First, ActionVectorEntry.EventTime))
12054  {
12055  ;
12056  }
12057  ActionVectorEntry.Command = Second;
12058  ActionVectorEntry.NewDescription = Third;
12059  }
12060  else if(FormatType == TimeCmdMaxSpeed) //new at v2.21.0
12061  {
12062  if(CheckTimeValidity(45, First, ActionVectorEntry.EventTime))
12063  {
12064  ;
12065  }
12066  ActionVectorEntry.Command = Second;
12067  ActionVectorEntry.NewMaxSpeed = Third;
12068  }
12069  TempTrainDataEntry.ActionVector.push_back(ActionVectorEntry);
12070  }
12071  }
12072  else // last entry, & not entered under signaller control with no repeats, i.e. could be finish or repeat
12073  {
12074  OneEntry = NewRemainder;
12075  First = "";
12076  Second = "";
12077  Third = "";
12078  Fourth = "";
12079  RearStartOrRepeatMins = 0;
12080  FrontStartOrRepeatDigits = 0;
12081  NumberOfRepeats = 0;
12082  if((FinishFlag) && (OneEntry[1] != 'R'))
12083  // already had a finish entry
12084  {
12085  TimetableMessage(GiveMessages, "Error in timetable - Last event = '" + OneEntry + "'. An earlier finish event has been found with something other than a repeat following it - only a repeat can follow a finish event.");
12086  Utilities->CallLogPop(79);
12087  return(false);
12088  }
12089  if(OneEntry[1] != 'R') // must be finish
12090  {
12091  if(!SplitEntry(1, OneEntry, GiveMessages, CheckLocationsExistInRailway, First, Second, Third, Fourth, RearStartOrRepeatMins,
12092  FrontStartOrRepeatDigits, FormatType, LocationType, SequenceType, ShuttleLinkType, ExitList, Warning))
12093  {
12094  TimetableMessage(GiveMessages, "Error in timetable - Event: '" + OneEntry + "'.\nIf the program version is not the latest the "
12095  "timetable may have features that aren't compatible with the version in use.");
12096  Utilities->CallLogPop(757);
12097  return(false);
12098  }
12099  //below added at v2.14.0 to prevent unpowered trains attempting to form a new service.
12100 /* restriction removed at v2.19.0
12101  if(NewTrain && (PowerAtRail < 1) && ((Second == "Fns") || (Second == "Frh-sh") || (Second == "Fns-sh") || (Second == "F-nshs")))
12102  {
12103  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry +
12104  "': a train created without power can't form a new service.");
12105  Utilities->CallLogPop(2548);
12106  return(false);
12107  }
12108  //end of new additions
12109 */
12110  if(SequenceType != FinishSequence)
12111  {
12112  TimetableMessage(GiveMessages, "Error in timetable - last event should be a finish: '" + OneEntry + "'");
12113  Utilities->CallLogPop(785);
12114  return(false);
12115  }
12116  if(FinalCall)
12117  {
12118  // interpret & add to ActionVector
12119  TDateTime TempTime;
12120  TActionVectorEntry ActionVectorEntry;
12121  ActionVectorEntry.FormatType = FormatType;
12122  ActionVectorEntry.LocationType = LocationType;
12123  ActionVectorEntry.SequenceType = SequenceType;
12124  ActionVectorEntry.ShuttleLinkType = ShuttleLinkType;
12125  ActionVectorEntry.Warning = Warning;
12126  if(FormatType == TimeCmd)
12127  {
12128  if(CheckTimeValidity(11, First, ActionVectorEntry.EventTime))
12129  {
12130  ;
12131  }
12132  ActionVectorEntry.Command = Second;
12133  }
12134  else if(FormatType == TimeCmdHeadCode)
12135  {
12136  if(CheckTimeValidity(12, First, ActionVectorEntry.EventTime))
12137  {
12138  ;
12139  }
12140  ActionVectorEntry.Command = Second;
12141  ActionVectorEntry.OtherHeadCode = Third;
12142  }
12143  else if(FormatType == FNSNonRepeatToShuttle)
12144  {
12145  if(CheckTimeValidity(13, First, ActionVectorEntry.EventTime))
12146  {
12147  ;
12148  }
12149  ActionVectorEntry.Command = Second;
12150  ActionVectorEntry.NonRepeatingShuttleLinkHeadCode = Third;
12151  }
12152  else if(FormatType == FSHNewService)
12153  {
12154  if(CheckTimeValidity(14, First, ActionVectorEntry.EventTime))
12155  {
12156  ;
12157  }
12158  ActionVectorEntry.Command = Second;
12159  ActionVectorEntry.OtherHeadCode = Third;
12160  ActionVectorEntry.NonRepeatingShuttleLinkHeadCode = Fourth;
12161  }
12162  else if(FormatType == ExitRailway)
12163  {
12164  if(CheckTimeValidity(19, First, ActionVectorEntry.EventTime))
12165  {
12166  ;
12167  }
12168  ActionVectorEntry.Command = Second;
12169  ActionVectorEntry.ExitList = ExitList;
12170  }
12171  else if(FormatType == FinRemHere)
12172  {
12173  ActionVectorEntry.Command = Second;
12174  }
12175  TempTrainDataEntry.ActionVector.push_back(ActionVectorEntry);
12176  }
12177  }
12178  else // repeat
12179  {
12180  if(!SplitRepeat(0, OneEntry, RearStartOrRepeatMins, FrontStartOrRepeatDigits, NumberOfRepeats, GiveMessages))
12181  {
12182  Utilities->CallLogPop(786);
12183  // error messages given in SplitRepeat
12184  return(false);
12185  }
12186  if(FinalCall)
12187  {
12188  TActionVectorEntry ActionVectorEntry;
12189  ActionVectorEntry.FormatType = Repeat;
12190  ActionVectorEntry.LocationType = LocTypeForRepeatEntry;
12191  ActionVectorEntry.SequenceType = SequTypeForRepeatEntry;
12192  ActionVectorEntry.ShuttleLinkType = ShuttleLinkTypeForRepeatEntry;
12193  ActionVectorEntry.RearStartOrRepeatMins = RearStartOrRepeatMins;
12194  ActionVectorEntry.FrontStartOrRepeatDigits = FrontStartOrRepeatDigits;
12195  ActionVectorEntry.NumberOfRepeats = NumberOfRepeats;
12196  TempTrainDataEntry.ActionVector.push_back(ActionVectorEntry);
12197  }
12198  }
12199  }
12200  }
12201  if(FinalCall)
12202  {
12203  TrainDataVector.push_back(TempTrainDataEntry);
12204  }
12205  }
12206  Utilities->CallLogPop(80);
12207  return(true);
12208 }
12209 
12210 // ---------------------------------------------------------------------------
12211 
12212 bool TTrainController::Last2CharactersBothDigits(int Caller, AnsiString HeadCode)
12213 {
12214  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",Last2CharactersBothDigits," + HeadCode);
12215  if((HeadCode[HeadCode.Length() - 1] < '0') || (HeadCode[HeadCode.Length() - 1] > '9'))
12216  {
12217  Utilities->CallLogPop(1890);
12218  return(false);
12219  }
12220  if((HeadCode[HeadCode.Length()] < '0') || (HeadCode[HeadCode.Length()] > '9'))
12221  {
12222  Utilities->CallLogPop(1891);
12223  return(false);
12224  }
12225  Utilities->CallLogPop(1892);
12226  return(true);
12227 }
12228 
12229 // ---------------------------------------------------------------------------
12230 
12231 bool TTrainController::CheckTimeValidity(int Caller, AnsiString TimeStr, TDateTime &Time)
12232 // 1st 5 chars must be HH:MM, anything else will be ignored
12233 {
12234  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckTimeValidity," + TimeStr);
12235  if(TimeStr.Length() < 5)
12236  {
12237  Utilities->CallLogPop(926);
12238  return(false);
12239  }
12240  if((TimeStr[1] < '0') || (TimeStr[1] > '9'))
12241  {
12242  Utilities->CallLogPop(927);
12243  return(false);
12244  }
12245  if((TimeStr[2] < '0') || (TimeStr[2] > '9'))
12246  {
12247  Utilities->CallLogPop(928);
12248  return(false);
12249  }
12250  if(TimeStr[3] != ':')
12251  {
12252  Utilities->CallLogPop(929);
12253  return(false);
12254  }
12255  if((TimeStr[4] < '0') || (TimeStr[4] > '5'))
12256  {
12257  Utilities->CallLogPop(930);
12258  return(false);
12259  }
12260  if((TimeStr[5] < '0') || (TimeStr[5] > '9'))
12261  {
12262  Utilities->CallLogPop(931);
12263  return(false);
12264  }
12265  while(TimeStr.Length() > 5)
12266  {
12267  TimeStr = TimeStr.SubString(1, TimeStr.Length() - 1);
12268  }
12269  double WholeHours = (AnsiString(TimeStr[1]) + AnsiString(TimeStr[2])).ToDouble();
12270  double FracHour = ((AnsiString(TimeStr[4]) + AnsiString(TimeStr[5])).ToDouble()) / 60.0;
12271 
12272  if((WholeHours + FracHour) >= 95.98334)
12273  {
12274  Utilities->CallLogPop(1817);
12275  return(false); // > 95h 59m
12276  }
12277  Time = TDateTime((WholeHours + FracHour) / 24);
12278  Utilities->CallLogPop(932);
12279  return(true);
12280 }
12281 
12282 // ---------------------------------------------------------------------------
12283 
12284 bool TTrainController::SplitEntry(int Caller, AnsiString OneEntry, bool GiveMessages, bool CheckLocationsExistInRailway, AnsiString &First, AnsiString &Second,
12285  AnsiString &Third, AnsiString &Fourth, int &RearStartOrRepeatMins, int &FrontStartOrRepeatDigits, TTimetableFormatType &FormatType,
12286  TTimetableLocationType &LocationType, TTimetableSequenceType &SequenceType, TTimetableShuttleLinkType &ShuttleLinkType, TNumList &ExitList, bool &Warning)
12287 /* This is a train action entry from a single line of the timetable, i.e. not train information and not a repeat entry.
12288  Return false for failure.
12289  See description above under ProcessOneTimetableLinefor details of train action entries
12290  NB all types set except LocationType for Snt as may be located or not
12291 */{
12292  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SplitEntry," + OneEntry);
12293  Warning = false;
12294  TDateTime TempTime;
12295 
12296  if(OneEntry.Length() > 0)
12297  {
12298  if(OneEntry[1] == 'W') // warning
12299  {
12300  Warning = true;
12301  OneEntry = OneEntry.SubString(2, OneEntry.Length() - 1);
12302  // strip it off
12303  }
12304  }
12305  if(OneEntry == "Frh")
12306  {
12307  FormatType = FinRemHere;
12308  SequenceType = FinishSequence;
12309  LocationType = AtLocation;
12310  ShuttleLinkType = NotAShuttleLink;
12311  Second = "Frh";
12312  Utilities->CallLogPop(1016);
12313  return(true);
12314  }
12315  if(OneEntry.Length() < 7)
12316  {
12317  Utilities->CallLogPop(907);
12318  return(false); // 'HH:MM;' + at least a one-letter location name
12319  }
12320  int Pos = OneEntry.Pos(';'); // first segment delimiter
12321 
12322  if(Pos != 6)
12323  {
12324  Utilities->CallLogPop(908);
12325  return(false);
12326  // no delimiter or delimiter not in position 6, has to be a time so fail
12327  }
12328  First = OneEntry.SubString(1, 5); // has to be a time
12329  if(!CheckTimeValidity(16, First, TempTime))
12330  {
12331  Utilities->CallLogPop(909);
12332  return(false);
12333  }
12334  AnsiString Remainder = OneEntry.SubString(Pos + 1, OneEntry.Length() - Pos);
12335 
12336 // if((Remainder[1] >= '0') && (Remainder[1] <= '9')) changed at v2.16.0 so only 'digit-digit-colon....' interpreted as a time - to allow locations to begin with digits
12337  if((Remainder.Length() >= 3) && (Remainder[1] >= '0') && (Remainder[1] <= '9') && (Remainder[2] >= '0') && (Remainder[2] <= '9') && (Remainder[3] == ':'))
12338  // next segment is a time so this is a TimeTimeLoc & 3rd seg has to be a location to be valid
12339  {
12340  if(Remainder.Length() < 7)
12341  {
12342  Utilities->CallLogPop(910);
12343  return(false); // 'HH:MM;' + at least a one-letter location name
12344  }
12345  Pos = Remainder.Pos(';'); // second segment delimiter
12346  if(Pos == 0)
12347  {
12348  Utilities->CallLogPop(911);
12349  return(false);
12350  // no delimiter, has to be one between departure time & location
12351  }
12352  Second = Remainder.SubString(1, 5); // has to be a time
12353  if(!CheckTimeValidity(15, Second, TempTime))
12354  {
12355  Utilities->CallLogPop(912);
12356  return(false);
12357  }
12358  Third = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
12359  if(!CheckLocationValidity(0, Third, GiveMessages, CheckLocationsExistInRailway))
12360  {
12361  Utilities->CallLogPop(913);
12362  return(false);
12363  }
12364  FormatType = TimeTimeLoc;
12365  SequenceType = IntermediateSequence;
12366  LocationType = AtLocation;
12367  ShuttleLinkType = NotAShuttleLink;
12368  Utilities->CallLogPop(914);
12369  return(true);
12370  }
12371  Pos = Remainder.Pos(';'); // second segment delimiter
12372  if(Pos == 0) // no third segment so second must be a location, or cdt
12373  {
12374  Second = Remainder;
12375  if(Second == "cdt")
12376  {
12377  FormatType = TimeCmd;
12378  ShuttleLinkType = NotAShuttleLink;
12379  LocationType = AtLocation;
12380  SequenceType = IntermediateSequence;
12381  Utilities->CallLogPop(915);
12382  return(true);
12383  }
12384  if(!CheckLocationValidity(1, Second, GiveMessages, CheckLocationsExistInRailway))
12385  {
12386  Utilities->CallLogPop(916);
12387  return(false);
12388  }
12389  else
12390  {
12391  FormatType = TimeLoc;
12392  LocationType = AtLocation;
12393  SequenceType = IntermediateSequence;
12394  ShuttleLinkType = NotAShuttleLink;
12395  Utilities->CallLogPop(917);
12396  return(true);
12397  }
12398  }
12399  // here if second segment is a command, with a third & maybe fourth segments
12400  if((Pos != 4) && (Pos != 7) && (Pos != 8))
12401  {
12402  Utilities->CallLogPop(918);
12403  return(false);
12404  // no third segement or not in position 4 or 7, & should be since all commands are 3, 6 or 7 letters
12405  }
12406  Second = Remainder.SubString(1, Pos - 1); // command
12407 
12408  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
12409  // details
12410  Pos = Remainder.Pos(';'); // third segment delimiter
12411  if(Pos == 0)
12412  {
12413  Third = Remainder;
12414  }
12415  else
12416  {
12417  Third = Remainder.SubString(1, Pos - 1);
12418  Fourth = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
12419  }
12420 
12421  if((Second == "Snt") || (Second == "Snt-sh"))
12422  // third has to be 2 element idents with a space between
12423  {
12424  int SpacePos = Third.Pos(' ');
12425  if(SpacePos == 0)
12426  {
12427  Utilities->CallLogPop(919);
12428  return(false); // no space
12429  }
12430  AnsiString RearStartStr = Third.SubString(1, SpacePos - 1);
12431  AnsiString FrontStartStr = Third.SubString(SpacePos + 1, Third.Length() - SpacePos);
12432  // int RearPosition=0, FrontPosition=0, RearExitPos=0;
12433  if(CheckLocationsExistInRailway)
12434  {
12435  if(!CheckStartPositionValidity(0, RearStartStr, FrontStartStr, GiveMessages))
12436  {
12437  Utilities->CallLogPop(920);
12438  return(false);
12439  }
12440  RearStartOrRepeatMins = Track->GetTrackVectorPositionFromString(3, RearStartStr, GiveMessages);
12441  FrontStartOrRepeatDigits = Track->GetTrackVectorPositionFromString(4, FrontStartStr, GiveMessages);
12442  }
12443  if(Second == "Snt")
12444  {
12445  FormatType = StartNew;
12446  SequenceType = StartSequence;
12447  LocationType = NoLocation;
12448  // can't be set until know whether located or not - done in SecondPassActions
12449  ShuttleLinkType = NotAShuttleLink;
12450  }
12451  else // Snt-sh
12452  {
12453  FormatType = SNTShuttle;
12454  LocationType = AtLocation;
12455  SequenceType = StartSequence;
12456  ShuttleLinkType = ShuttleLink;
12457  if(!CheckHeadCodeValidity(0, GiveMessages, Fourth))
12458  {
12459  Utilities->CallLogPop(1038);
12460  return(false);
12461  }
12462  }
12463  Utilities->CallLogPop(921);
12464  return(true);
12465  }
12466  if(Second == "Sns-sh") // third & fourth have to be headcodes
12467  {
12468  FormatType = SNSShuttle;
12469  LocationType = AtLocation;
12470  SequenceType = StartSequence;
12471  ShuttleLinkType = ShuttleLink;
12472  if(!CheckHeadCodeValidity(1, GiveMessages, Third))
12473  {
12474  Utilities->CallLogPop(1039);
12475  return(false);
12476  }
12477  if(!CheckHeadCodeValidity(2, GiveMessages, Fourth))
12478  {
12479  Utilities->CallLogPop(1040);
12480  return(false);
12481  }
12482  Utilities->CallLogPop(1041);
12483  return(true);
12484  }
12485  if(Second == "F-nshs")
12486  {
12487  FormatType = FNSNonRepeatToShuttle;
12488  LocationType = AtLocation;
12489  SequenceType = FinishSequence;
12490  ShuttleLinkType = ShuttleLink;
12491  if(!CheckHeadCodeValidity(3, GiveMessages, Third))
12492  {
12493  Utilities->CallLogPop(1047);
12494  return(false);
12495  }
12496  Utilities->CallLogPop(1048);
12497  return(true);
12498  }
12499  if(Second == "Sns-fsh")
12500  {
12501  FormatType = SNSNonRepeatFromShuttle;
12502  LocationType = AtLocation;
12503  SequenceType = StartSequence;
12504  ShuttleLinkType = ShuttleLink;
12505  if(!CheckHeadCodeValidity(4, GiveMessages, Third))
12506  {
12507  Utilities->CallLogPop(1098);
12508  return(false);
12509  }
12510  Utilities->CallLogPop(1099);
12511  return(true);
12512  }
12513  if(Second == "Fns-sh") // third & fourth have to be headcodes
12514  {
12515  FormatType = FSHNewService;
12516  LocationType = AtLocation;
12517  SequenceType = FinishSequence;
12518  ShuttleLinkType = ShuttleLink;
12519  if(!CheckHeadCodeValidity(5, GiveMessages, Third))
12520  {
12521  Utilities->CallLogPop(1050);
12522  return(false);
12523  }
12524  if(!CheckHeadCodeValidity(6, GiveMessages, Fourth))
12525  {
12526  Utilities->CallLogPop(1051);
12527  return(false);
12528  }
12529  Utilities->CallLogPop(1052);
12530  return(true);
12531  }
12532  // new segment for 'pas'
12533  if(Second == "pas") // third has to be a location
12534  {
12535  FormatType = PassTime;
12536  LocationType = EnRoute;
12537  SequenceType = IntermediateSequence;
12538  ShuttleLinkType = NotAShuttleLink;
12539  if(!CheckLocationValidity(2, Third, GiveMessages, CheckLocationsExistInRailway))
12540  {
12541  Utilities->CallLogPop(1515);
12542  return(false);
12543  }
12544  Utilities->CallLogPop(1516);
12545  return(true);
12546  }
12547  // new segment for revised 'Fer'
12548  if(Second == "Fer")
12549  // third has to be a set of IDs separated by spaces, and at least 1
12550  {
12551  FormatType = ExitRailway;
12552  LocationType = EnRoute;
12553  SequenceType = FinishSequence;
12554  ShuttleLinkType = NotAShuttleLink;
12555  if(CheckLocationsExistInRailway)
12556  {
12557  if(!CheckAndPopulateListOfIDs(0, Third, ExitList, GiveMessages))
12558  {
12559  Utilities->CallLogPop(1519);
12560  return(false);
12561  }
12562  }
12563  Utilities->CallLogPop(1520);
12564  return(true);
12565  }
12566  if(Second == "dsc") //new at v2.15.0 - change description
12567  {
12568  if(Third.Length() > 60)
12569  {
12570  TimetableMessage(GiveMessages, "Train description too long, limit of 60 characters in '" + Third + "'");
12571  Utilities->CallLogPop(2582);
12572  return(false);
12573  }
12574  for(int x = 1; x < Third.Length() + 1; x++)
12575  {
12576 // if((Third[x] < ' ') || (Third[x] > '~')) changed at v2.16.0 to allow extended characters in location names
12577  if((Third[x] < ' ') && (Third[x] >= 0))
12578  {
12579  TimetableMessage(GiveMessages, "Train description contains invalid characters in '" + Third + "'");
12580  Utilities->CallLogPop(2583);
12581  return(false);
12582  }
12583  }
12584  FormatType = TimeCmdDescription;
12585  LocationType = AtLocation;
12586  SequenceType = IntermediateSequence;
12587  ShuttleLinkType = NotAShuttleLink;
12588  Utilities->CallLogPop(2604);
12589  return(true);
12590  }
12591  if(Second == "cms") //new at v2.21.0 - change max speed
12592  {
12593  if(Third == "")
12594  {
12595  TimetableMessage(GiveMessages, "New maximum speed value missing");
12596  Utilities->CallLogPop(2704);
12597  return(false);
12598  }
12599  for(int x = 1; x < Third.Length() + 1; x++)
12600  {
12601  if((Third[x] < '0') || (Third[x] > '9'))
12602  {
12603  TimetableMessage(GiveMessages, "Train maximum speed must be a number in '" + Third + "'");
12604  Utilities->CallLogPop(2705);
12605  return(false);
12606  }
12607  }
12608  int MaxRunningSpeed = Third.ToInt(); //must be a whole positive number here
12609  if(MaxRunningSpeed > TTrain::MaximumSpeedLimit) // 400kph = 250mph
12610  {
12611  TimetableMessage(GiveMessages, "Train maximum running speed [" + Third + "km/h] can't be greater than 400km/h");
12612  Utilities->CallLogPop(2706);
12613  return(false);
12614  }
12615  if(MaxRunningSpeed < 10)
12616  // changed at v0.6 to prevent low max speeds - can cause problems in SetTrainMovementValues
12617  {
12618  TimetableMessage(GiveMessages, "Train maximum running speed [" + Third + "km/h] can't be less than 10km/h.");
12619  Utilities->CallLogPop(2707);
12620  return(false);
12621  }
12622  FormatType = TimeCmdMaxSpeed;
12623  LocationType = AtLocation;
12624  SequenceType = IntermediateSequence;
12625  ShuttleLinkType = NotAShuttleLink;
12626  Utilities->CallLogPop(2708);
12627  return(true);
12628  }
12629 
12630 // if((Second == "fsp") || (Second == "fsp")) then there can optionlly be a fourth: xx-yy where xx = percentage mass & yy = percentage power in the split train
12631 
12632  // all remainder must be TimeCmdHeadCode types to be valid
12633  if((Second != "Fns") && (Second != "Fjo") && (Second != "jbo") && (Second != "fsp") && (Second != "rsp") && (Second != "Sfs") && (Second != "Sns") &&
12634  (Second != "Frh-sh"))
12635  {
12636  Utilities->CallLogPop(922);
12637  return(false); // all TimeCmdHeadCode types
12638  }
12639  if(!CheckHeadCodeValidity(7, GiveMessages, Third))
12640  {
12641  Utilities->CallLogPop(923);
12642  return(false);
12643  }
12644  FormatType = TimeCmdHeadCode;
12645  LocationType = AtLocation;
12646  if(Second == "Frh-sh")
12647  {
12648  ShuttleLinkType = ShuttleLink;
12649  }
12650  else
12651  {
12652  ShuttleLinkType = NotAShuttleLink;
12653  }
12654  if((Second == "Fns") || (Second == "Fjo") || (Second == "Frh-sh"))
12655  {
12656  SequenceType = FinishSequence;
12657  }
12658  if((Second == "jbo") || (Second == "fsp") || (Second == "rsp"))
12659  {
12660  SequenceType = IntermediateSequence;
12661  }
12662  if((Second == "Sfs") || (Second == "Sns"))
12663  {
12664  SequenceType = StartSequence;
12665  }
12666  //new at v2.15.0 to allow splits to have different characteristics - Fourth specifies: AA-BB where AA & BB can be 1 or 2 digits, AA is percentage mass
12667  if((Fourth != "") && ((Second == "fsp") || (Second == "rsp"))) //& BB is percentage power allocated to the split off train
12668  {
12669  if(!CheckFourthValidityForSplit(Fourth, GiveMessages))
12670  {
12671  Utilities->CallLogPop(2584);
12672  return(false);
12673  }
12674  }
12675  Utilities->CallLogPop(924);
12676  return(true);
12677 }
12678 
12679 // ---------------------------------------------------------------------------
12680 
12681 bool TTrainController::CheckFourthValidityForSplit(AnsiString SplitDistributionString, bool GiveMessages) //new at v2.15.0
12682 {
12683  Utilities->CallLog.push_back(Utilities->TimeStamp() + ",CheckFourthValidityForSplit," + SplitDistributionString);
12684  bool ErrorFlag = false;
12685  int x, y;
12686  if((SplitDistributionString.Length() > 6 ) || (SplitDistributionString.Length() < 3))
12687  {
12688  ErrorFlag = true;
12689  }
12690  int pos = SplitDistributionString.Pos('-');
12691  if(pos == 0)
12692  {
12693  ErrorFlag = true;
12694  }
12695  else
12696  {
12697  AnsiString MassStr = SplitDistributionString.SubString(1, pos - 1);
12698  AnsiString PowerStr = SplitDistributionString.SubString(pos + 1, SplitDistributionString.Length() - pos);
12699  try //allows for one or two digit percentages
12700  {
12701  int x = MassStr.ToInt();
12702  int y = PowerStr.ToInt();
12703  if((x > 99) || (x < 1) || (y > 100) || (y < 0))
12704  {
12705  ErrorFlag = true;
12706  }
12707  }
12708  catch(const Exception &e) //non-error catch
12709  {
12710  ErrorFlag = true;
12711  }
12712  }
12713  if(ErrorFlag)
12714  {
12715  TimetableMessage(GiveMessages, "Error in split distribution " + SplitDistributionString + ", should be 'AA-BB' where AA is the percentage mass (min 1, max 99) and BB the percentage " +
12716  "power for the new split-off train");
12717  Utilities->CallLogPop(2585);
12718  return(false);
12719  }
12720  Utilities->CallLogPop(2601);
12721  return(true);
12722 }
12723 
12724 // ---------------------------------------------------------------------------
12725 
12726 bool TTrainController::CheckLocationValidity(int Caller, AnsiString LocStr, bool GiveMessages, bool CheckLocationsExistInRailway)
12727 {
12728  // check that the location name exists in the railway (only if CheckLocationsExistInRailway is true), doesn't begin with 'digit-digit-colon'
12729  // and contains no control characters changed at v2.16.0 to allow extended characters in location names
12730  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckLocationValidity," + LocStr);
12731  if(LocStr == "")
12732  {
12733  Utilities->CallLogPop(1353);
12734  return(false); // has to have at least one character
12735  }
12736 // if((LocStr[1] >= '0') && (LocStr[1] <= '9')) //changed at v2.16.0 to allow locations to begine with digits, if 'digit-digit-colon' then must be a time
12737  if((LocStr.Length() >= 3) && (LocStr[1] >= '0') && (LocStr[1] <= '9') && (LocStr[2] >= '0') && (LocStr[2] <= '9') && (LocStr[3] == ':'))
12738  {
12739  Utilities->CallLogPop(1354);
12740  return(false); // can't begin with 'digit-digit-colon' as this regarded as a time
12741  }
12742  for(int x = 1; x < LocStr.Length() + 1; x++)
12743  {
12744  if(((LocStr[x] < ' ') && (LocStr[x] >= 0)) || (LocStr[x] == ',') || (LocStr[x] == ';')) //changed at v2.16.0 to allow extended characters in location names
12745  {
12746  Utilities->CallLogPop(1355);
12747  return(false); // contains a special character or ',' or ';'
12748  }
12749 /*
12750  if(LocStr[x] > 'z') //dropped at v2.16.0 to allow extended characters in location names
12751  {
12752  Utilities->CallLogPop(1356);
12753  return(false); // contains a character outside the standard ASCII set
12754  }
12755 */
12756  }
12757  // check exists in railway location list if CheckLocationsExistInRailway is true
12758  if(CheckLocationsExistInRailway)
12759  {
12760  if(!Track->TimetabledLocationNameAllocated(3, LocStr))
12761  {
12762  TimetableMessage(GiveMessages, "Location name '" + LocStr +
12763  "' appears in the timetable but is not a valid name. To be valid the name must be a stopping location and apply to one or more platforms " +
12764  "(not concourses on their own), or to track at a blue non-station named location. BUT NOTE THAT trains can't stop at continuations so a name " +
12765  "that includes a continuation will not be valid.");
12766  Utilities->CallLogPop(1357);
12767  return(false);
12768  }
12769  }
12770  Utilities->CallLogPop(1358);
12771  return(true);
12772 }
12773 
12774 // ---------------------------------------------------------------------------
12775 
12776 bool TTrainController::CheckHeadCodeValidity(int Caller, bool GiveMessages, AnsiString HeadCode)
12777 {
12778  // if(!AnyHeadCodeValid) up to 8 characters total & last 4 characters must be NLNN where N = number and L = capital or small letter
12779  // if(AnyHeadCodeValid) up to 8 characters total, last 2 chars must be digits & last but 2 can be any alphanumeric, upper or lower case
12780  // NOTE: As of v0.6b AnyHeadCodeValid dropped, all headcodes are unrestricted
12781  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString((short)GiveMessages) + ",CheckHeadCodeValidity," +
12782  HeadCode);
12783  if((HeadCode.Length() < 4) || (HeadCode.Length() > 8))
12784  {
12785  TimetableMessage(GiveMessages, "Headcode error in '" + HeadCode +
12786  "', length must be between 4 and 8 characters, and last 4 must be a legitimate headcode. This error can also be caused by omitting a service reference after Sns, Snt-sh, Sns-sh, Fns, Fns-sh or Frh-sh");
12787  Utilities->CallLogPop(1359);
12788  return(false);
12789  }
12790  // firstly allow any printable character (ASCII >= CHAR(32) & <= CHAR(126)), as these allowed in 1st 4 characters
12791  for(int x = 1; x < (HeadCode.Length() + 1); x++)
12792  {
12793  if((HeadCode[x] < ' ') || (HeadCode[x] > '~'))
12794  {
12795  TimetableMessage(GiveMessages, "Non-printable character in headcode '" + HeadCode + "'");
12796  Utilities->CallLogPop(1895);
12797  return(false);
12798  }
12799  }
12800  // secondly ensure the true Headcode only has letters or digits
12801  for(int x = 3; x >= 0; x--)
12802  {
12803  if(((HeadCode[HeadCode.Length() - x] < 'A') || (HeadCode[HeadCode.Length() - x] > 'Z')) && ((HeadCode[HeadCode.Length() - x] < 'a') ||
12804  (HeadCode[HeadCode.Length() - x] > 'z')) && ((HeadCode[HeadCode.Length() - x] < '0') || (HeadCode[HeadCode.Length() - x] > '9')))
12805  {
12806  TimetableMessage(GiveMessages, "Headcode error in '" + HeadCode + "', headcode must consist of letters and digits only");
12807  Utilities->CallLogPop(1790);
12808  return(false);
12809  }
12810  }
12811  Utilities->CallLogPop(1364);
12812  return(true);
12813 }
12814 
12815 // ---------------------------------------------------------------------------
12816 
12817 bool TTrainController::CheckAndPopulateListOfIDs(int Caller, AnsiString IDSet, TNumList &ExitList, bool GiveMessages)
12818 // set of track element IDs, separated by spaces, and at least 1 present
12819 {
12820  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckAndPopulateListOfIDs," + IDSet); //had wrong title, changed at v2.13.0
12821  ExitList.clear();
12822  AnsiString CurrentID = "";
12823 
12824  if(IDSet.Length() == 0)
12825  {
12826  TimetableMessage(GiveMessages, "Must have at least one exit element ID following 'Fer'");
12827  Utilities->CallLogPop(1521);
12828  return(false);
12829  }
12830  for(int x = 1; x <= IDSet.Length(); x++)
12831  {
12832  char C = IDSet[x];
12833  if(((C < '0') || (C > '9')) && (C != ' ') && (C != 'N') && (C != '-'))
12834  {
12835  TimetableMessage(GiveMessages, "Illegal character in the set of element IDs following 'Fer' in '" + IDSet + "'");
12836  Utilities->CallLogPop(1522);
12837  return(false);
12838  }
12839 /* don't use, error checks in GetTrackVectorPositionFromString instead
12840  if(C == '-') //this section added at v2.13.0 because of Amon Sadler's error file submitted 24/03/22
12841  {
12842  if((x==1) || (x == IDSet.Length()))
12843  {
12844  TimetableMessage(GiveMessages, "Illegal minus character ('-') in the set of element IDs following 'Fer' in '" + IDSet + "'");
12845  Utilities->CallLogPop(2479);
12846  return(false);
12847  }
12848  if((IDSet[x-1] < '0') || (IDSet[x-1] > '9') || (IDSet[x+1] < '0') || (IDSet[x+1] > '9'))
12849  {
12850  TimetableMessage(GiveMessages, "Illegal minus character ('-') in the set of element IDs following 'Fer' in '" + IDSet + "'");
12851  Utilities->CallLogPop(2480);
12852  return(false);
12853  }
12854  }
12855 */
12856  }
12857  int Pos = IDSet.Pos(' '); // look for the first space
12858 
12859  while(true)
12860  {
12861  if(Pos == 0)
12862  {
12863  CurrentID = IDSet;
12864  IDSet = "";
12865  }
12866  else
12867  {
12868  CurrentID = IDSet.SubString(1, Pos - 1);
12869  IDSet = IDSet.SubString(Pos + 1, IDSet.Length() - Pos);
12870  }
12871  int VecPos = Track->GetTrackVectorPositionFromString(7, CurrentID, GiveMessages);
12872  if(VecPos == -1)
12873  {
12874  Utilities->CallLogPop(1523);
12875  return(false); // messages given in GetTrackVectorPositionFromString
12876  }
12877  else
12878  {
12879  if(Track->TrackElementAt(722, VecPos).TrackType != Continuation)
12880  {
12881  TimetableMessage(GiveMessages, "The element ID '" + CurrentID + "' following 'Fer' is not an exit");
12882  Utilities->CallLogPop(1524);
12883  return(false);
12884  }
12885  else
12886  {
12887  // first check for duplicates
12888  if(!ExitList.empty())
12889  {
12890  for(TNumListIterator ELIT = ExitList.begin(); ELIT != ExitList.end(); ELIT++)
12891  {
12892  if(*ELIT == VecPos)
12893  {
12894  TimetableMessage(GiveMessages, "The element ID '" + CurrentID + "' following 'Fer' duplicates an earlier element");
12895  Utilities->CallLogPop(1532);
12896  return(false);
12897  }
12898  }
12899  }
12900  // of OK add it to the list
12901  ExitList.push_back(VecPos);
12902  }
12903  }
12904  if(IDSet == "")
12905  {
12906  Utilities->CallLogPop(1525);
12907  return(true);
12908  }
12909  else
12910  {
12911  Pos = IDSet.Pos(' '); // look for the next space
12912  }
12913  } // while(true)
12914 }
12915 
12916 // ---------------------------------------------------------------------------
12917 bool TTrainController::SplitTrainInfo(int Caller, AnsiString TrainInfoStr, AnsiString &HeadCode, AnsiString &Description, int &StartSpeed, int &MaxRunningSpeed,
12918  int &Mass, double &MaxBrakeRate, double &PowerAtRail, int &SignallerSpeed, bool GiveMessages)
12919 // 7 or 8 items for a new train (6 or 7 semicolons), for a continuing service only need headcode, though can have a description, if other
12920 // data entered for continuing service then will be ignored - message given to warn user, checks appropriate number of items and validity
12921 // of each item
12922 {
12923  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SplitTrainInfo," + TrainInfoStr);
12924  int Pos = 0;
12925  AnsiString Remainder = "";
12926  int SemiColonCount = 0;
12927 
12928  for(int x = 1; x < TrainInfoStr.Length() + 1; x++)
12929  {
12930  if(TrainInfoStr[x] == ';')
12931  {
12932  SemiColonCount++;
12933  }
12934  }
12935  if((SemiColonCount != 6) && (SemiColonCount != 7) && (SemiColonCount != 1) && (SemiColonCount != 0))
12936  {
12937  TimetableMessage(GiveMessages, "Error in train information in '" + TrainInfoStr +
12938  "'. Should be headcode + optional description for a continuing service;" +
12939  " or headcode, description, start speed, max running speed, mass, brake force, power (and optional signaller max. speed) for a new service");
12940  Utilities->CallLogPop(880);
12941  return(false);
12942  }
12943  if(SemiColonCount == 0)
12944  {
12945  HeadCode = TrainInfoStr;
12946  if(!CheckHeadCodeValidity(8, GiveMessages, HeadCode))
12947  {
12948  Utilities->CallLogPop(881);
12949  return(false);
12950  }
12951  Utilities->CallLogPop(882);
12952  return(true);
12953  }
12954  if(SemiColonCount == 1) // headcode & description only
12955  {
12956  Pos = TrainInfoStr.Pos(';'); // 1st delimiter
12957  HeadCode = TrainInfoStr.SubString(1, Pos - 1);
12958  Description = TrainInfoStr.SubString(Pos + 1, TrainInfoStr.Length() - Pos);
12959  if(!CheckHeadCodeValidity(9, GiveMessages, HeadCode))
12960  {
12961  Utilities->CallLogPop(883);
12962  return(false);
12963  }
12964  if(Description == "")
12965  {
12966  TimetableMessage(GiveMessages, "Train description missing in '" + TrainInfoStr + "'");
12967  Utilities->CallLogPop(884);
12968  return(false);
12969  }
12970  if(Description.Length() > 60)
12971  {
12972  TimetableMessage(GiveMessages, "Train description too long, limit of 60 characters '" + TrainInfoStr + "'");
12973  Utilities->CallLogPop(1157);
12974  return(false);
12975  }
12976  for(int x = 1; x < Description.Length() + 1; x++)
12977  {
12978 // if((Description[x] < ' ') || (Description[x] > '~')) changed at v2.16.0 to allow extended characters in location names
12979  if((Description[x] < ' ') && (Description[x] >= 0))
12980  {
12981  TimetableMessage(GiveMessages, "Train description contains invalid characters in '" + TrainInfoStr + "'");
12982  Utilities->CallLogPop(885);
12983  return(false);
12984  }
12985  }
12986  Utilities->CallLogPop(886);
12987  return(true);
12988  }
12989  // if here must have 6 or 7 semicolons
12990  Pos = TrainInfoStr.Pos(';'); // 1st delimiter
12991  HeadCode = TrainInfoStr.SubString(1, Pos - 1);
12992  Remainder = TrainInfoStr.SubString(Pos + 1, TrainInfoStr.Length() - Pos);
12993  if(!CheckHeadCodeValidity(10, GiveMessages, HeadCode))
12994  {
12995  Utilities->CallLogPop(887);
12996  return(false);
12997  }
12998  Pos = Remainder.Pos(';'); // 2nd delimiter
12999  Description = Remainder.SubString(1, Pos - 1);
13000  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
13001  if(Description == "")
13002  {
13003  TimetableMessage(GiveMessages, "Train description missing in '" + TrainInfoStr + "'");
13004  Utilities->CallLogPop(888);
13005  return(false);
13006  }
13007  if(Description.Length() > 60)
13008  {
13009  TimetableMessage(GiveMessages, "Train description too long, limit of 60 characters '" + TrainInfoStr + "'");
13010  Utilities->CallLogPop(1158);
13011  return(false);
13012  }
13013  for(int x = 1; x < Description.Length() + 1; x++)
13014  {
13015 // if((Description[x] < ' ') || (Description[x] > 126)) changed at v2.16.0 to allow extended characters in location names
13016  if((Description[x] < ' ') && (Description[x] >= 0))
13017  {
13018  TimetableMessage(GiveMessages, "Train description contains invalid characters in '" + TrainInfoStr + "'");
13019  Utilities->CallLogPop(889);
13020  return(false);
13021  }
13022  }
13023  Pos = Remainder.Pos(';'); // 3rd delimiter
13024  AnsiString StartSpeedStr = Remainder.SubString(1, Pos - 1);
13025 
13026  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
13027  if(StartSpeedStr == "")
13028  {
13029  TimetableMessage(GiveMessages, "Train starting speed missing in '" + TrainInfoStr + "'");
13030  Utilities->CallLogPop(890);
13031  return(false);
13032  }
13033  for(int x = 1; x < StartSpeedStr.Length() + 1; x++)
13034  {
13035  if((StartSpeedStr[x] < '0') || (StartSpeedStr[x] > '9'))
13036  {
13037  TimetableMessage(GiveMessages, "Train start speed contains invalid characters in '" + TrainInfoStr + "'");
13038  Utilities->CallLogPop(891);
13039  return(false);
13040  }
13041  }
13042  StartSpeed = StartSpeedStr.ToInt();
13043  if(StartSpeed > TTrain::MaximumSpeedLimit) // 400kph = 250mph
13044  {
13045  StartSpeed = TTrain::MaximumSpeedLimit;
13046  if(!SSHigh) // added at v2.4.0
13047  {
13048  TimetableMessage(GiveMessages, "Train starting speed > 400km/h in '" + TrainInfoStr + "'. Setting it to 400km/h");
13049  SSHigh = true;
13050  }
13051  }
13052  Pos = Remainder.Pos(';'); // 4th delimiter
13053  AnsiString MaxRunningSpeedStr = Remainder.SubString(1, Pos - 1);
13054 
13055  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
13056  if(MaxRunningSpeedStr == "")
13057  {
13058  TimetableMessage(GiveMessages, "Train maximum running speed missing in '" + TrainInfoStr + "'");
13059  Utilities->CallLogPop(892);
13060  return(false);
13061  }
13062  for(int x = 1; x < MaxRunningSpeedStr.Length() + 1; x++)
13063  {
13064  if((MaxRunningSpeedStr[x] < '0') || (MaxRunningSpeedStr[x] > '9'))
13065  {
13066  TimetableMessage(GiveMessages, "Train maximum running speed contains invalid characters in '" + TrainInfoStr + "'");
13067  Utilities->CallLogPop(893);
13068  return(false);
13069  }
13070  }
13071  MaxRunningSpeed = MaxRunningSpeedStr.ToInt();
13072  if(MaxRunningSpeed > TTrain::MaximumSpeedLimit) // 400kph = 250mph
13073  {
13074  TimetableMessage(GiveMessages, "Train maximum running speed [" + MaxRunningSpeedStr + "km/h] can't be greater than 400km/h");
13075  Utilities->CallLogPop(2709);
13076  return(false);
13077  }
13078  if(MaxRunningSpeed < 10)
13079  // changed at v0.6 to prevent low max speeds - can cause problems in SetTrainMovementValues
13080  {
13081  TimetableMessage(GiveMessages, "Train maximum running speed [" + MaxRunningSpeedStr + "km/h] can't be less than 10km/h.");
13082  Utilities->CallLogPop(2710);
13083  return(false);
13084  }
13085  Pos = Remainder.Pos(';'); // 5th delimiter
13086  AnsiString MassStr = Remainder.SubString(1, Pos - 1);
13087 
13088  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
13089  if(MassStr == "")
13090  {
13091  TimetableMessage(GiveMessages, "Train mass missing in '" + TrainInfoStr + "'");
13092  Utilities->CallLogPop(895);
13093  return(false);
13094  }
13095  for(int x = 1; x < MassStr.Length() + 1; x++)
13096  {
13097  if((MassStr[x] < '0') || (MassStr[x] > '9'))
13098  {
13099  TimetableMessage(GiveMessages, "Train mass contains invalid characters in '" + TrainInfoStr + "'");
13100  Utilities->CallLogPop(896);
13101  return(false);
13102  }
13103  }
13104  Mass = MassStr.ToInt() * 1000; // convert tonnes to kg
13105  if(Mass > TTrain::MaximumMassLimit) // 10,000tonnes
13106  {
13107  Mass = TTrain::MaximumMassLimit;
13108  if(!MassHigh) // added at v2.4.0
13109  {
13110  TimetableMessage(GiveMessages, "Train mass > 10,000 tonnes in '" + TrainInfoStr + "'. Setting it to 10,000 tonnes");
13111  MassHigh = true;
13112  }
13113  }
13114  if(Mass == 0)
13115  {
13116  TimetableMessage(GiveMessages, "Train mass zero in '" + TrainInfoStr + "'");
13117  Utilities->CallLogPop(897);
13118  return(false);
13119  }
13120  Pos = Remainder.Pos(';'); // 6th delimiter
13121  AnsiString MaxBrakeForceStr = Remainder.SubString(1, Pos - 1);
13122 
13123  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
13124  if(MaxBrakeForceStr == "")
13125  {
13126  TimetableMessage(GiveMessages, "Train braking force missing in '" + TrainInfoStr + "'");
13127  Utilities->CallLogPop(898);
13128  return(false);
13129  }
13130  for(int x = 1; x < (MaxBrakeForceStr.Length() + 1); x++)
13131  {
13132  if((MaxBrakeForceStr[x] != '.') && ((MaxBrakeForceStr[x] < '0') || (MaxBrakeForceStr[x] > '9')))
13133  {
13134  TimetableMessage(GiveMessages, "Train braking force contains invalid characters in '" + TrainInfoStr + "'");
13135  Utilities->CallLogPop(899);
13136  return(false);
13137  }
13138  }
13139  double MaxBrakeForce = MaxBrakeForceStr.ToDouble() * 1000;
13140 
13141  // convert to kg force
13142  if((MaxBrakeForce / Mass) > 1) // gives 'g' braking - 9.81m/s/s
13143  {
13144  MaxBrakeForce = Mass;
13145  if(!BFHigh) // added at v2.4.0
13146  {
13147  TimetableMessage(GiveMessages, "Train braking force too high in '" + TrainInfoStr + "'. Setting it to the same as the train mass");
13148  BFHigh = true;
13149  }
13150  }
13151  if((MaxBrakeForce / Mass) < 0.01)
13152  {
13153  MaxBrakeForce = Mass * 0.01;
13154  if(!BFLow) // added at v2.4.0
13155  {
13156  TimetableMessage(GiveMessages, "Train braking force too low in '" + TrainInfoStr + "'. Setting it to 1% of the train mass");
13157  BFLow = true;
13158  }
13159  }
13160  // convert to m/s/s
13161  MaxBrakeRate = MaxBrakeForce / Mass * 9.81;
13162  // now may have just a power entry or power and signaller max. speed
13163  AnsiString GrossPowerStr = "", SignallerSpeedStr = "";
13164 
13165  if(SemiColonCount == 6)
13166  {
13167  GrossPowerStr = Remainder;
13168  SignallerSpeedStr = "30"; // default value
13169  }
13170  else // must be 7
13171  {
13172  Pos = Remainder.Pos(';'); // 7th delimiter
13173  GrossPowerStr = Remainder.SubString(1, Pos - 1);
13174  SignallerSpeedStr = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
13175  }
13176  // deal with GrossPower
13177  if(GrossPowerStr == "")
13178  {
13179  TimetableMessage(GiveMessages, "Train power missing in '" + TrainInfoStr + "'");
13180  Utilities->CallLogPop(901);
13181  return(false);
13182  }
13183  for(int x = 1; x < GrossPowerStr.Length() + 1; x++)
13184  {
13185  if((GrossPowerStr[x] < '0') || (GrossPowerStr[x] > '9'))
13186  {
13187  TimetableMessage(GiveMessages, "Train power contains invalid characters in '" + TrainInfoStr + "'");
13188  Utilities->CallLogPop(902);
13189  return(false);
13190  }
13191  }
13192 
13193  double GrossPower = GrossPowerStr.ToInt() * 1000; // convert to W
13194 
13195  if(GrossPower > TTrain::MaximumPowerLimit) // 100MW
13196  {
13197  GrossPower = TTrain::MaximumPowerLimit;
13198  if(!PwrHigh)
13199  {
13200  TimetableMessage(GiveMessages, "Train power > 100,000kW in '" + TrainInfoStr + "'. Setting it to 100,000kW");
13201  PwrHigh = true;
13202  }
13203  }
13204  else if(GrossPower == 0) // changed at v2.4.0
13205  {
13206  GrossPower = 0.1;
13207  // can't be zero or AValue is zero and then have divide by zero error, so set to 0.1W so acceleration tiny (though should be intercepted before accel calculated)
13208  }
13209  else if((GrossPower > 0) && (GrossPower < 10000))
13210  // added at v2.4.0 to ensure min power of 8kW at rail unless zero (otherwise could have too low AValues
13211  {
13212  GrossPower = 10000;
13213  }
13214  PowerAtRail = GrossPower * 0.8;
13215  // apply ratio of 80% for rail to gross power (seems about average from an internet search)
13216 
13217  // deal with SignallerSpeed
13218  if(SignallerSpeedStr == "")
13219  {
13220  TimetableMessage(GiveMessages, "Signaller speed not set in '" + TrainInfoStr + "', either set a value or remove the extra semicolon");
13221  Utilities->CallLogPop(1771);
13222  return(false);
13223  }
13224  for(int x = 1; x < SignallerSpeedStr.Length() + 1; x++)
13225  {
13226  if((SignallerSpeedStr[x] < '0') || (SignallerSpeedStr[x] > '9'))
13227  {
13228  TimetableMessage(GiveMessages, "Signaller speed contains invalid characters in '" + TrainInfoStr + "'");
13229  Utilities->CallLogPop(1769);
13230  return(false);
13231  }
13232  }
13233  SignallerSpeed = SignallerSpeedStr.ToInt();
13234  if(SignallerSpeed > TTrain::MaximumSpeedLimit)
13235  {
13236  SignallerSpeed = TTrain::MaximumSpeedLimit;
13237  if(!SigSHigh)
13238  {
13239  TimetableMessage(GiveMessages, "Signaller speed > 400km/h in '" + TrainInfoStr + "'. Setting it to 400km/h");
13240  SigSHigh = true;
13241  }
13242  }
13243  if(SignallerSpeed < 10)
13244  // changed at v0.6 to prevent low max speeds - can cause problems in SetTrainMovementValues
13245  {
13246  SignallerSpeed = 10;
13247  if(!SigSLow)
13248  {
13249  TimetableMessage(GiveMessages, "Signaller speed can't be less than 10km/h in '" + TrainInfoStr + "', it will be set to 10km/h");
13250  SigSLow = true;
13251  }
13252  // Utilities->CallLogPop(1770);
13253  // return false;
13254  }
13255  Utilities->CallLogPop(904);
13256  return(true);
13257 }
13258 
13259 // ---------------------------------------------------------------------------
13260 
13261 bool TTrainController::SplitRepeat(int Caller, AnsiString OneEntry, int &RearStartOrRepeatMins, int &FrontStartOrRepeatDigits, int &NumberOfRepeats,
13262  bool GiveMessages)
13263 {
13264  // Format must be: R;mm;dd;nn mm may be 1, 2 or more digits, dd may be 1 or 2 digits, nn may be 1, 2 or more digits
13265  // function checks validity of each item and returns false for error
13266  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SplitRepeat," + OneEntry);
13267  if(OneEntry.Length() < 7)
13268  {
13269  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - should be 'R;m;d;n'");
13270  Utilities->CallLogPop(865);
13271  return(false);
13272  }
13273  int SemiColonCount = 0;
13274 
13275  for(int x = 1; x < OneEntry.Length() + 1; x++)
13276  {
13277  if(OneEntry[x] == ';')
13278  {
13279  SemiColonCount++;
13280  }
13281  }
13282  if(SemiColonCount != 3)
13283  {
13284  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - should be 'R;m;d;n'");
13285  Utilities->CallLogPop(866);
13286  return(false);
13287  }
13288  if((OneEntry[1] != 'R') || (OneEntry[2] != ';'))
13289  {
13290  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - should be 'R;m;d;n'");
13291  Utilities->CallLogPop(867);
13292  return(false);
13293  }
13294  AnsiString Remainder = OneEntry.SubString(3, OneEntry.Length() - 2);
13295  // strip off R;
13296 
13297  int Pos = 0;
13298 
13299  Pos = Remainder.Pos(';');
13300  AnsiString MinutesStr = Remainder.SubString(1, Pos - 1);
13301 
13302  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
13303  if(MinutesStr == "")
13304  {
13305  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - minute increment segment missing");
13306  Utilities->CallLogPop(868);
13307  return(false);
13308  }
13309  if(MinutesStr.Length() > 3)
13310  // added for v2.3.1 following Albie Vowles' reported error in repeat value 03/02/20
13311  {
13312  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - minute value too high, maximum value is 999");
13313  Utilities->CallLogPop(2119);
13314  return(false);
13315  }
13316  for(int x = 1; x < MinutesStr.Length() + 1; x++)
13317  {
13318  if((MinutesStr[x] < '0') || (MinutesStr[x] > '9'))
13319  {
13320  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - non-digit character in minute increment segment");
13321  Utilities->CallLogPop(869);
13322  return(false);
13323  }
13324  }
13325  RearStartOrRepeatMins = MinutesStr.ToInt();
13326  if(RearStartOrRepeatMins == 0)
13327  {
13328  TimetableMessage(GiveMessages, "Repeat minute increment is zero in: '" + OneEntry + "' - can't have a zero value");
13329  Utilities->CallLogPop(870);
13330  return(false);
13331  }
13332  Pos = Remainder.Pos(';');
13333  AnsiString DigitsStr = Remainder.SubString(1, Pos - 1);
13334 
13335  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
13336  if(DigitsStr == "")
13337  {
13338  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - headcode increment segment missing");
13339  Utilities->CallLogPop(871);
13340  return(false);
13341  }
13342  for(int x = 1; x < DigitsStr.Length() + 1; x++)
13343  {
13344  if((DigitsStr[x] < '0') || (DigitsStr[x] > '9'))
13345  {
13346  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - non-digit character in headcode increment segment");
13347  Utilities->CallLogPop(872);
13348  return(false);
13349  }
13350  }
13351  if(DigitsStr.Length() > 2)
13352  {
13353  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - maximum number of digits for headcode increment is 2");
13354  Utilities->CallLogPop(873);
13355  return(false);
13356  }
13357  FrontStartOrRepeatDigits = DigitsStr.ToInt();
13358 /* allow zero digit increments so HC can stay same for repeated services - for many suburban services the headcode digits relate to the
13359  route rather than the service
13360  if(FrontStartOrRepeatDigits == 0)
13361  {
13362  TimetableMessage(GiveMessages, "Repeat headcode increment is zero in: '" + OneEntry + "' - can't have a zero value");
13363  Utilities->CallLogPop(874);
13364  return false;
13365  }
13366 */
13367  if(!Last2CharactersBothDigits(0, ServiceReference) && (FrontStartOrRepeatDigits > 0))
13368  // new for v0.6b for unrestricted headcodes
13369  {
13370  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry +
13371  "' - a repeating service with incrementing digits must have digits as its last two headcode characters");
13372  Utilities->CallLogPop(1889);
13373  return(false);
13374  }
13375  AnsiString NumberStr = Remainder;
13376 
13377  if(NumberStr == "")
13378  {
13379  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - number of repeats missing");
13380  Utilities->CallLogPop(875);
13381  return(false);
13382  }
13383  if(NumberStr.Length() > 4)
13384  // added for v2.3.1 following Albie Vowles' reported error 03/02/20
13385  {
13386  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - repeat value too high, no timetabled event can exceed 95 hours & 59 minutes");
13387  Utilities->CallLogPop(2118);
13388  return(false);
13389  }
13390  for(int x = 1; x < NumberStr.Length() + 1; x++)
13391  {
13392  if((NumberStr[x] < '0') || (NumberStr[x] > '9'))
13393  // catches negative numbers
13394  {
13395  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - non-digit character in number of repeats");
13396  Utilities->CallLogPop(876);
13397  return(false);
13398  }
13399  }
13400  NumberOfRepeats = NumberStr.ToInt();
13401  if(NumberOfRepeats == 0)
13402  {
13403  TimetableMessage(GiveMessages, "Number of repeats is zero in: '" + OneEntry + "' - if no repeats are needed the repeat should be omitted");
13404  Utilities->CallLogPop(877);
13405  return(false);
13406  }
13407  Utilities->CallLogPop(878);
13408  return(true);
13409 }
13410 
13411 // ---------------------------------------------------------------------------
13412 
13413 bool TTrainController::SecondPassActions(int Caller, bool GiveMessages, bool &TwoLocationFlag) //TwoLocationFlag added at v2.9.1
13414 /* Note that here the TrainDataVector has been compiled with FinalCall true in ProcessOneTimetableLine so work on the
13415  vector rather than the timetable
13416  Note also that for unlocated Snt entries the LocationType hasn't yet been set
13417 
13418  Many of the errors caught here duplicate those in the preliminary checks, but leave in for completeness
13419 
13420 Updated significantly for v2.15.0. Current procedure:-
13421 
13422 Preliminary checks for v0.2b without changing anything, carry each out separately:-
13423  1) must have at least one actionvector entry
13424  2) if first actionvector entry not SignallerControl then must have at least one more actionvector entry
13425  3) if first actionvector entry is SignallerControl then must have no more actionvector entries except a repeat
13426  4) first entry must be a start;
13427  4a) if first entry is Snt and second is a finish then it can't be Fns-sh or Frh-sh
13428  4b) if first entry is Sns or Sfs and second is a finish then it must be either Frh or Fjo
13429  4c) if first entry is Snt-sh, Sns-sh or Sns-fsh second can't be a finish
13430  5) a start must be the first entry;
13431  6) a repeat entry must be the last;
13432  7) for other than SignallerControl the last entry must be repeat or finish; if last entry is a repeat the last but one must be a finish;
13433  8) a finish entry must be the last or last but one, and if last but one the last must be a repeat
13434  Other successor errors will be caught later as all 'throws' changed to messages prior to the bulk of the sucessor checks
13435 
13436 Set location for located Snt or Snt-sh and ensure successor AtLocation
13437 For unlocated Snt-sh give error message
13438 For unlocated Snt & not sig control check successor moving
13439 
13440 Check all other starts (all located) have valid successors
13441 
13442 Set location for Sns-sh and Sns-fsh from following TimeLoc, if not one then give message
13443 
13444 Carry out linkage checks to ensure all links present, no data set yet & locations not checked yet. First check for duplicates, then for cross
13445 references, then for non-repeating shuttle cross refs. This is done because the later location naming functions give error messages if there
13446 are missing links.
13447 
13448 Set names for all Fns finishes from earlier named event or fail if can't find
13449 Set names for linked Sns events with same event times from above, but first carry out immediate successor checks and give error message for:-
13450 no successors, moving successor, another start sequence, a finish that isn't Frh or Fjo or a repeat. No error messages given here for location
13451 not found, that check done later.
13452 
13453 Trap errors where rsp/fsp follows an Sfs without a TimeLoc arrival before (or unlikely to be able to set fsp/rsp/Sfs location because Sfs locs set
13454 from linked fsp/rsp events)
13455 
13456 Name all fsp/rsp events, then check that all named or give error message.
13457 
13458 Set all Sfs names from above fsp/rsp links with same event times, but first carry out immediate successor checks and give error message for:-
13459 no successors, moving successor, another start sequence, a finish that isn't Frh or Fjo or a repeat. No error messages given here for location
13460 not found, that check done later.
13461 
13462 Set remaining AtLoc Command locations from preceding named event
13463 
13464 All location names should now be set
13465 
13466 Final detailed check of names for all AtLoc Commands. If find any without a name give an error message:-
13467 If jbo, fsp, rsp, cdt dsc or cms say must be preceded by a named event at same location, normally an arrival
13468 If Sns or Sfs say to make sure the linked finish event is preceded by a named event at same location, normally an arrival
13469 If Snt-sh say to make sure that the service starts with zero speed and is at a named location
13470 If Sns-fsh or Sns-sh say to make sure that the event is followed (not necessarily immediately) by a departure
13471 If Frh, Fns, Fjo, Frh-sh, Fns-sh or F-nshs say that the event must be preceded by an event at the same location that has an identified location name,
13472 normally an arrival.
13473 Missing: pas & Fer not AtLoc, Snt whether located or not covered in detail earlier.
13474 
13475 Later checks as before 2.15.0 changes:-
13476 
13477 Check remaining successor validity except for TimeLoc arr & dep since those times not set yet
13478 
13479 Set arrival & departure times for TimeLocs & set their EventTimes to -1 (up to now all have times as EventTime)
13480 
13481 Perform remaining successor checks for TimeLocs
13482 
13483 Check all TimeLocs have either Arr or Dep time set and EventTime == -1, all Cmds have EventTime set & Arr & Dep times == -1, & repeats have no times
13484 set
13485 
13486 Check times stay same or increase through a service, note that can have time of 0 if include midnight
13487 
13488 Check locations consistent
13489 
13490 Check same location doesn't appear twice before a cdt except for separate arr & dep TimeLocs (just a potential error warning given in v2.6.0) i.e.
13491 same location can appear in any number of consecutive entries but once changed couldn't repeat before a direction change prior to v2.6.0. Message
13492 given in InterfaceUnit
13493 
13494 Check all locations except unlocated 'Snt' & 'Fer' have LocationName set and throw error if not.
13495 
13496 Carry out full cross reference and duplicate link checks for all services inc shuttles, and set data and check location consistency
13497 
13498 Check that each shuttle start ends either in Fns or Fxx-sh (though a single service can't end in Fxx-sh), and that
13499 when the Fxx-sh is reached it references the original start and not another shuttle - not allowed to link two shuttles, don't ever need to and as
13500 designed would skip repeats
13501 
13502 Check all entries have all types set to something and throw error if not
13503 
13504 All OK if reach here, so set up the TrainOperatingDataVector (already has one entry) & NumberOfTrains
13505 
13506 Check that don't include any Continuation names
13507 
13508 Check that all repeat times below 96h
13509 
13510 Now that all set up change any extended headcodes back to ordinary headcodes (had been service references until now.
13511 
13512 Finally call BuildContinuationTrainExpectationMultiMap
13513 
13514 ***********************************
13515 
13516 For info:-
13517 class TActionVectorEntry //contains a single train action - repeat entry is also of this class though no train action is taken for it
13518 {
13519 public:
13520 AnsiString LocationName, Command, OtherHeadCode, NonRepeatingShuttleLinkHeadCode; ///< string values for timetabled event entries, null
13522 bool SignallerControl; ///< indicates a train that is defined by the timetable as under signaller control
13523 bool Warning; ///< if set triggers an alert in the warning panel when the action is reached
13524 int NumberOfRepeats; ///< the number of repeating services
13525 int RearStartOrRepeatMins, FrontStartOrRepeatDigits; ///< dual-purpose variables used for the TrackVectorPositions of the rear and front
13527 TDateTime EventTime, ArrivalTime, DepartureTime; ///< relevant times at which the action is timetabled, zeroed on creation so change
13529 TNumList ExitList; ///< the list of valid train exit TrackVector positions for 'Fer' entries (empty to begin with)
13530 TTimetableFormatType FormatType; ///< defines the timetable action type
13531 TTimetableLocationType LocationType; ///< indicates where the train is when the relevant action occurs
13532 TTimetableSequenceType SequenceType; ///< indicates where in the sequence of codes the action lies
13533 TTimetableShuttleLinkType ShuttleLinkType; ///< indicates whether or not the action relates to a shuttle service link
13534 TTrainDataEntry *LinkedTrainEntryPtr; ///< link pointer for use between fsp/rsp & Sfs; Fjo & jbo; Fns & Sns; & all shuttle to shuttle
13536 TTrainDataEntry *NonRepeatingShuttleLinkEntryPtr; ///< pointer used by shuttles for the non-shuttle train links, in & out, the
13538 
13539 // inline function
13540 
13542 TActionVectorEntry() {
13543 RearStartOrRepeatMins=0; FrontStartOrRepeatDigits=0; NumberOfRepeats=0; FormatType=NoFormat;
13544 SequenceType=NoSequence; LocationType=NoLocation; ShuttleLinkType=NoShuttleLink, EventTime=TDateTime(-1);
13545 ArrivalTime=TDateTime(-1); DepartureTime=TDateTime(-1); LinkedTrainEntryPtr=0; NonRepeatingShuttleLinkEntryPtr=0;
13546 Warning = false; SignallerControl = false;
13547 }
13548 };
13549 
13550 typedef std::vector<TActionVectorEntry> TActionVector;//contains all actions for a single train
13551 
13552 class TTrainDataEntry //contains all data for a single train - copied into train object when becomes active
13553 {
13554 public:
13555 AnsiString HeadCode, ServiceReference, Description; ///< headcode is the first train's headcode, rest are calculated from repeat
13558 double MaxBrakeRate; ///< in metres/sec/sec
13559 double MaxRunningSpeed; ///< in km/h
13560 double PowerAtRail; ///< in Watts (taken as 80% of the train's Gross Power, i.e. that entered by the user)
13561 int Mass; ///< in kg
13562 int NumberOfTrains; ///< number of repeats + 1
13563 int SignallerSpeed; ///< in km/h for use when under signaller control
13564 int StartSpeed; ///< in km/h
13565 TActionVector ActionVector; ///< all the actions for the train
13566 TTrainOperatingDataVector TrainOperatingDataVector; ///< operating information for the train including all its repeats
13567 
13568 //inline function
13569 
13571 TTrainDataEntry()
13572 {
13573 StartSpeed=0; MaxRunningSpeed=0; NumberOfTrains=0;
13574 }
13575 };
13576 
13577 Allowable successors:-
13578 Snt unlocated -> Fer, TimeLoc (arr), TimeTimeLoc, (new) pas; No others
13579 Snt located -> No starts, no finishes except Frh, Fjo (as of v2.0.0), Fns, and F-nshs, no repeat, pas, TimeTimeLoc or TimeLoc arr;
13580 any other cmd or TimeLoc (dep) OK
13581 Snt-sh -> No starts, finishes, repeats, pas or TimeTimeLoc; any other cmd or TimeLoc (dep) OK
13582 Sfs -> No starts, finishes except Frh & Fjo (as of v2.15.0), repeats, pas, TimeTimeLoc, TimeLoc arr, rsp, fsp; any other cmd or
13583 TimeLoc (dep) OK [must have departure & arrival before another split]
13584 Sns -> No starts, finishes except Frh & Fjo (as of v2.15.0), repeats, pas, TimeTimeLoc or TimeLoc arr; any other cmd or TimeLoc (dep) OK
13585 Sns-sh -> No starts, finishes, repeats, pas or TimeTimeLoc; any other cmd or TimeLoc (dep) OK (must have a TimeLoc departure somewhere in
13586 sequence to
13587 set location, else fails)
13588 Sns-fsh -> No starts, finishes, repeats, pas or TimeTimeLoc; any other cmd or TimeLoc (dep) OK (must have a TimeLoc departure somewhere in
13589 sequence to
13590 set location, else fails)
13591 Fns -> R only [must be preceded by a TimeLoc arrival at the finish location, not necessarily immediately]
13592 F-nshs -> Nothing (no repeats permitted)
13593 Fjo -> R only
13594 Frh -> R only
13595 Fer -> R only
13596 Frh-sh -> R only
13597 Fns-sh -> R only
13598 jbo -> No starts, repeats, pas, Fer or TimeTimeLoc; TimeLoc (dep), others OK [must be preceded by an event whose location is set]
13599 fsp -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK [must be preceded by an event whose location is set]
13600 rsp -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK [must be preceded by an event whose location is set]
13601 cdt -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK [must be preceded by an event whose location is set]
13602 dsc -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK [must be preceded by an event whose location is set]
13603 cms -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK [must be preceded by an event whose location is set]
13604 TimeLoc (arr) -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK
13605 TimeLoc (dep) -> Fer, TimeLoc (arr), or TimeTimeLoc, (new) pas OK, no others
13606 TimeTimeLoc -> Fer, TimeLoc (arr), or TimeTimeLoc, (new) pas OK, no others
13607 (new) pas -> Fer, TimeLoc (arr), or TimeTimeLoc, (new) pas OK, no others
13608 Repeat -> Nothing
13609 
13610 There must be a TimeLoc arrival (or a Sns start at location) in a sequence so successive cmd locations can be set
13611 Check all Snt's & set Locations if located (located = zero start speed, either element at a location (but if rear element
13612 is a continuation then treated as unlocated), and location listed in the next TimeLoc entry, though needn't be immediately after)
13613 If Snt entry at a location specified in a following TimeLoc entry but start speed > 0 give error message
13614 Check all times increase or stay same through ActionVector
13615 Cycle through all entries in vector setting arr & dep times based on above list
13616 Add locations to all relevant cmd entries based on earlier arrival location (or earlier reference for Sfs & Sns)
13617 Check locations match the arr & dep TimeLoc entries
13618 Check same location doesn't appear twice before a cdt except for separate arr & dep TimeLocs
13619 Make above valid succession checks
13620 Check all splits have matching Sfs headcodes (both ways), add locations to Sfs's & check times same [Sfs loc derived from preceding fsp/rsp
13621 loc]
13622 Check all new service headcodes (Sns) have matching headcodes (both ways), add locations to Sns's & check times same [Sns loc derived from
13623 preceding Fns loc]
13624 Check a split to 'x' doesn't again split to 'x' (anywhere, not just for one train, since headcodes can be duplicated)
13625 Check each Fns has matching Sns headcodes (both ways), add locations to Fns's & check times same
13626 Check all joins have matching headcodes (both ways), locations & times & don't occur in same sequence
13627 Check each joined by train not joined by same train again (anywhere, not just for one train, since headcodes can be duplicated)
13628 Set train info for Sfs & Sns entries
13629 Check each repeat entry exactly matches any included joins or splits (user has to enter it to show that really wants it)
13630 Check at least one platform long enough for a split (only need 2 lengths) & disallow if not, need length of 2 & 1 extra
13631 element at each end, or length of 3 & 1 extra element at either end
13632 Check all TimeLocs have either Arr or Dep times set and EventTime == -1
13633 Check all Cmds have EventTime set & Arr & Dep times = -1
13634 Check all locations except unlocated Snts, Fers and Repeats have a LocationName
13635 
13636 Give messages in function if errors detected and clear the vector. Return false for failure.
13637 */
13638 
13639 /* Earlier checks:-
13640 Checks carried out with error messages in this function:-
13641 At least one comma in the line (it's based on a csv file);
13642 No entries following train information;
13643 At least one comma in remainder after train information (i.e at least a start and a finish entry);
13644 SplitEntry returns false in an intermediate entry - message repeats the entry for information;
13645 First entry not a start entry;
13646 Train information incomplete before a start entry;
13647 Entry follows a finish entry but doesn't begin with 'R';
13648 SplitEntry returns false in a finish entry - message repeats the entry for information;
13649 Last action entry isn't a finish entry.
13650 
13651 Function returns false with no message if:-
13652 Timetable start time invalid (no message is given for an invalid time as the line is assumed to be an irrelevant line; if no start
13653 time is found at all then an error message is given in the calling function);
13654 SplitTrainInfo returns false (message given in called function);
13655 SplitRepeat returns false (message given in called function).
13656 
13657 Double crosslink (shuttle) table: [OtherHeadCode, NonRepeatingShuttleLinkHeadCode, LinkedTrainEntryPtr, NonRepeatingShuttleLinkEntryPtr] <-- these for
13658 easier searching for this table
13659 
13660 Command Format OtherHead NonRepeating- LinkedTrain- NonRepeating- Decsription
13661  Code ShuttleLink- EntryPtr ShuttleLink
13662  HeadCode EntryPtr
13663 
13664 Snt-sh SNTShuttle Y (rtn shuttle) N Y (rtn sh) N Simple shuttle - no feeder service
13665 Frh-sh TimeCmdHeadCode Y (outwd shuttle) N Y (outwd sh) N Simple shuttle - no finishing service
13666 F-nshs FNSNonRepeatToShuttle N (shld be Y for outwd shuttle) Y (shld be N) Y (correct) N (correct) Feeder service link to shuttle
13667 Sns-sh SNSShuttle Y (rtn shuttle) Y (feeder) Y (fdr-shld be rtn)N (shld be fdr) Luckily NonRep link not needed
13668 Sns-fsh SNSNonRepeatFromShuttle N (shld be Y for rtn shuttle) Y (shld be N) Y (correct) N (correct) Finishing service link from shuttle
13669 Fns-sh FSHNewService Y (outwd shuttle) Y (finishing) Y (outwd sh) Y (finish) Shuttle link to finishing service
13670 
13671 Note: Any shuttle start can have any finish - feeder and finish, neither, feeder but no finish & vice versa.
13672 
13673 */
13674 {
13675  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SecondPassActions,");
13676  if(TrainDataVector.empty())
13677  {
13678  SecondPassMessage(GiveMessages, "Error in timetable - there appear to be no train services in the timetable, it must contain at least one");
13679  TrainDataVector.clear();
13680  Utilities->CallLogPop(1832);
13681  return(false);
13682  }
13683 /* new preliminary checks for v0.2b without changing anything, carry each out separately:-
13684  1) must have at least one actionvector entry
13685  2) if first actionvector entry not SignallerControl then must have at least one more actionvector entry
13686  3) if first actionvector entry is SignallerControl then must have no more actionvector entries except a repeat
13687  4) first entry must be a start;
13688  4a) if first entry is Snt and second is a finish then it can't be Fns-sh or Frh-sh
13689  4b) if first entry is Sns or Sfs and second is a finish then it must be either Frh or Fjo
13690  4c) if first entry is Snt-sh, Sns-sh or Sns-fsh second can't be a finish
13691  5) a start must be the first entry;
13692  6) a repeat entry must be the last;
13693  7) for other than SignallerControl the last entry must be repeat or finish; if last entry is a repeat the last but one must be a finish;
13694  8) a finish entry must be the last or last but one, and if last but one the last must be a repeat
13695  Other successor errors will be caught later as all 'throws' changed to messages prior to the bulk of the sucessor checks
13696 */
13697 
13698  TwoLocationList.clear(); //empty the list to begin with, added at v2.9.1
13699  TwoLocationFlag = false; //added at v2.9.1
13700  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (1)
13701  {
13702  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13703  if(TrainDataVector.at(x).ActionVector.empty())
13704  {
13705  SecondPassMessage(GiveMessages, "Error in timetable - the following service has no listed events, there must be at least one: " + TDEntry.HeadCode);
13706  TrainDataVector.clear();
13707  Utilities->CallLogPop(1833);
13708  return(false);
13709  }
13710  }
13711  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (2)
13712  {
13713  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13714  TActionVectorEntry AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
13715  if(!(AVEntry0.SignallerControl))
13716  {
13717  if(TrainDataVector.at(x).ActionVector.size() == 1)
13718  {
13719  SecondPassMessage(GiveMessages, "Error in timetable - service must have a start event and at least one other for: " + TDEntry.HeadCode);
13720  TrainDataVector.clear();
13721  Utilities->CallLogPop(1822);
13722  return(false);
13723  }
13724  }
13725  }
13726  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (3)
13727  {
13728  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13729  TActionVectorEntry AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
13730  if(AVEntry0.SignallerControl)
13731  {
13732  if(TrainDataVector.at(x).ActionVector.size() > 2)
13733  {
13734  SecondPassMessage(GiveMessages,
13735  "Error in timetable - a signaller controlled service can have no more than one item (a repeat) after the start event, see: " +
13736  TDEntry.HeadCode);
13737  TrainDataVector.clear();
13738  Utilities->CallLogPop(1837);
13739  return(false);
13740  }
13741  if(TrainDataVector.at(x).ActionVector.size() > 1)
13742  {
13743  TActionVectorEntry AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
13744  if(AVEntry1.FormatType != Repeat)
13745  {
13746  SecondPassMessage(GiveMessages,
13747  "Error in timetable - a signaller controlled service cannot have any other than a repeat after the start event, see: " + TDEntry.HeadCode);
13748  TrainDataVector.clear();
13749  Utilities->CallLogPop(1838);
13750  return(false);
13751  }
13752  }
13753  }
13754  }
13755  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (4)
13756  {
13757  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13758  TActionVectorEntry AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
13759  if(AVEntry0.SequenceType != StartSequence)
13760  {
13761  SecondPassMessage(GiveMessages, "Error in timetable - the first event must be a start for: " + TDEntry.HeadCode);
13762  TrainDataVector.clear();
13763  Utilities->CallLogPop(1824);
13764  return(false);
13765  }
13766  if((AVEntry0.Command == "Snt") && !AVEntry0.SignallerControl) // (4a) sig control condition added so there is a second AVEntry
13767  // 4a added at v2.0.0. This is only a rough check, Fer only valid for an unlocated Snt
13768  // and others for a located Snt, but those checks done later
13769  {
13770  TActionVectorEntry AVEntry1 = TrainDataVector.at(x).ActionVector.at(1); // must be a second entry if first not signallercontrol
13771  if((AVEntry1.SequenceType == FinishSequence) && ((AVEntry1.Command == "Fns-sh") || (AVEntry1.Command == "Frh-sh")))
13772  {
13773  SecondPassMessage(GiveMessages, "Error in timetable - finish events Fns-sh and Frh-sh not permitted immediately after an Snt entry for: " +
13774  TDEntry.HeadCode); //these are the only AtLoc finishes not allowed
13775  TrainDataVector.clear();
13776  Utilities->CallLogPop(2046);
13777  return(false);
13778  }
13779  }
13780  if((AVEntry0.Command == "Sns") || (AVEntry0.Command == "Sfs")) // (4b)
13781  // 4b added at v2.15.0
13782  {
13783  TActionVectorEntry AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
13784  if((AVEntry1.SequenceType == FinishSequence) && (AVEntry1.Command != "Frh") && (AVEntry1.Command != "Fjo"))
13785  {
13786  SecondPassMessage(GiveMessages, "Error in timetable - only 'Frh' or 'Fjo' finish events are permitted immediately after "
13787  "an 'Sns' or 'Sfs' event for: " + TDEntry.HeadCode + ". The program is unable to determine the "
13788  "location of any other type of finish.");
13789  TrainDataVector.clear();
13790  Utilities->CallLogPop(2580);
13791  return(false);
13792  }
13793  }
13794  if((AVEntry0.Command == "Snt-sh") || (AVEntry0.Command == "Sns-sh") || (AVEntry0.Command == "Sns-fsh")) // (4c)
13795  // 4c added at v2.15.0
13796  {
13797  TActionVectorEntry AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
13798  if(AVEntry1.SequenceType == FinishSequence)
13799  {
13800  SecondPassMessage(GiveMessages, "Error in timetable - a finish event can't immediately follow an 'Snt-sh', 'Sns-sh' or 'Sns-fsh' "
13801  "event for: " + TDEntry.HeadCode);
13802  TrainDataVector.clear();
13803  Utilities->CallLogPop(2616);
13804  return(false);
13805  }
13806  }
13807  }
13808  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (5)
13809  {
13810  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13811  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13812  {
13813  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13814  if((AVEntry.SequenceType == StartSequence) && (y != 0))
13815  {
13816  SecondPassMessage(GiveMessages, "Error in timetable - a start event is present that is not the first event for: " + TDEntry.HeadCode);
13817  TrainDataVector.clear();
13818  Utilities->CallLogPop(1825);
13819  return(false);
13820  }
13821  }
13822  }
13823  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (6)
13824  {
13825  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13826  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13827  {
13828  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13829  if((AVEntry.FormatType == Repeat) && (y != (TrainDataVector.at(x).ActionVector.size() - 1)))
13830  {
13831  SecondPassMessage(GiveMessages, "Error in timetable - a repeat is present that is not the last item for: " + TDEntry.HeadCode);
13832  TrainDataVector.clear();
13833  Utilities->CallLogPop(1826);
13834  return(false);
13835  }
13836  }
13837  }
13838  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (7)
13839  {
13840  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13841  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13842  {
13843  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13844  if((y == 0) && AVEntry.SignallerControl)
13845  {
13846  break;
13847  }
13848  if(y == (TrainDataVector.at(x).ActionVector.size() - 1))
13849  {
13850  if((AVEntry.FormatType != Repeat) && (AVEntry.SequenceType != FinishSequence))
13851  {
13852  SecondPassMessage(GiveMessages, "Error in timetable - the last item must be either a finish event or a repeat for: " + TDEntry.HeadCode);
13853  TrainDataVector.clear();
13854  Utilities->CallLogPop(1827);
13855  return(false);
13856  }
13857  if(AVEntry.FormatType == Repeat)
13858  {
13859  const TActionVectorEntry &LastButOneAVEntry = TrainDataVector.at(x).ActionVector.at(y - 1);
13860  if(LastButOneAVEntry.SequenceType != FinishSequence)
13861  {
13862  SecondPassMessage(GiveMessages, "Error in timetable - the event immediately before the repeat must be a finish for: " + TDEntry.HeadCode);
13863  TrainDataVector.clear();
13864  Utilities->CallLogPop(1828);
13865  return(false);
13866  }
13867  }
13868  }
13869  }
13870  }
13871  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (8)
13872  {
13873  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13874  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13875  {
13876  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13877  if(AVEntry.SequenceType == FinishSequence)
13878  {
13879  if((y != (TrainDataVector.at(x).ActionVector.size() - 1)) && (y != (TrainDataVector.at(x).ActionVector.size() - 2)))
13880  {
13881  SecondPassMessage(GiveMessages, "Error in timetable - a finish event must be either the last or last but one for: " + TDEntry.HeadCode);
13882  TrainDataVector.clear();
13883  Utilities->CallLogPop(1829);
13884  return(false);
13885  }
13886  if(y == (TrainDataVector.at(x).ActionVector.size() - 2))
13887  {
13888  if(TrainDataVector.at(x).ActionVector.at(y + 1).FormatType != Repeat)
13889  {
13890  SecondPassMessage(GiveMessages, "Error in timetable - the only event that can follow a finish event is a repeat for: " + TDEntry.HeadCode);
13891  TrainDataVector.clear();
13892  Utilities->CallLogPop(1830);
13893  return(false);
13894  }
13895  }
13896  }
13897  }
13898  }
13899 
13900  // end of new preliminary checks
13901 
13902  // check start event successor validity
13903  // For Snt & Snt-sh set location if stopped, don't set any times yet
13904  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13905  {
13906  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13907  TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
13908  // use reference so can change internals where necessary
13909  if((AVEntry0.Command == "Snt") || (AVEntry0.Command == "Snt-sh"))
13910  {
13911  AnsiString LocationName = "";
13912  if(IsSNTEntryLocated(0, TDEntry, LocationName))
13913  // it is at a location
13914  {
13915  AVEntry0.LocationName = LocationName; //located Snt location name set
13916  AVEntry0.LocationType = AtLocation;
13917  // check successor validity for located Snt that isn't a SignallerControl entry
13918  if(!AVEntry0.SignallerControl)
13919  {
13920  const TActionVectorEntry &AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
13921  // at least 2 entries present checked in integrity check so (1) valid
13922  if(!AtLocSuccessor(AVEntry1))
13923  {
13924  // Frh following Snt-sh will return false in location check, so no need to check here
13925  SecondPassMessage(GiveMessages, "Error in timetable - stopped 'Snt' or 'Snt-sh' followed by an illegal event for: " +
13926  TDEntry.HeadCode + ". The event isn't valid for a stationary train.");
13927  TrainDataVector.clear();
13928  Utilities->CallLogPop(523);
13929  return(false);
13930  }
13931  }
13932  }
13933  else // check not Snt-sh & carry out successor validity checks for unlocated Snt that isn't a SignallerControl entry
13934  {
13935  if(AVEntry0.Command == "Snt-sh")
13936  {
13937  SecondPassMessage(GiveMessages, "Error in timetable - 'Snt-sh' event not at stop location for: " + TDEntry.HeadCode);
13938  TrainDataVector.clear();
13939  Utilities->CallLogPop(1042);
13940  return(false);
13941  }
13942  AVEntry0.LocationType = EnRoute;
13943  if(!AVEntry0.SignallerControl)
13944  {
13945  const TActionVectorEntry &AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
13946  // at least 2 entries checked in integrity check so (1) valid
13947  if(!MovingSuccessor(AVEntry1))
13948  {
13949  SecondPassMessage(GiveMessages, "Error in timetable - unlocated 'Snt' not followed by 'Fer', 'pas' or an arrival for: " +
13950  TDEntry.HeadCode);
13951  TrainDataVector.clear();
13952  Utilities->CallLogPop(790);
13953  return(false);
13954  }
13955  }
13956  }
13957  }
13958  // check other start successors, all AtLoc
13959  else if(AVEntry0.SequenceType == StartSequence)
13960  {
13961  const TActionVectorEntry &AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
13962  // at least 2 entries present checked in integrity check so (1) valid
13963  if(!AtLocSuccessor(AVEntry1))
13964  {
13965  SecondPassMessage(GiveMessages, "Error in timetable - 'Sfs', 'Sns', 'Sns-sh', 'Snt-fsh' or 'Sns-fsh' followed by an illegal event for: " +
13966  TDEntry.HeadCode + ". The event isn't valid for a stationary train.");
13967  TrainDataVector.clear();
13968  Utilities->CallLogPop(793);
13969  return(false);
13970  }
13971  }
13972  }
13973 
13974 
13975  // set Sns-sh & Sns-fsh locations same as following TimeLoc departure entry location, if no departure before end of sequence give error message
13976  for(unsigned int x = 0; x < TrainDataVector.size(); x++) //at v2.15.0 set Sfs & Sns locations from corresponding fsp/rsp & Fns entries, shuttles ok as they are
13977  {
13978  bool FoundFlag = false;
13979  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13980  TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
13981  // use reference so can change internals
13982  if((AVEntry0.Command == "Sns-sh") || (AVEntry0.Command == "Sns-fsh"))
13983  {
13984  for(unsigned int y = 1; y < TrainDataVector.at(x).ActionVector.size(); y++)
13985  {
13986  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y);
13987  if(AVEntry2.FormatType == TimeLoc)
13988  {
13989  FoundFlag = true;
13990  AVEntry0.LocationName = AVEntry2.LocationName; //Sns-sh & Sns-fsh location names set
13991  break;
13992  }
13993  }
13994  if(!FoundFlag)
13995  {
13996  SecondPassMessage(GiveMessages, "Error in timetable - no location departure following an 'Sns-sh' or 'Sns-fsh' event for: " + TDEntry.HeadCode);
13997  TrainDataVector.clear();
13998  Utilities->CallLogPop(851);
13999  return(false);
14000  }
14001  }
14002  }
14003 
14004 //carry out preliminary check on service ref linkages without setting any data - added at v2.15.0 as can be location errors if linked trains not present
14005 //first check for duplicates then linkages (also checked later but leave that in)
14006  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14007  {
14008  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14009  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14010  {
14011  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14012  if(AVEntry.OtherHeadCode != "")
14013  {
14014  if(!CheckForDuplicateCrossReferences(2, TDEntry.HeadCode, AVEntry.OtherHeadCode, GiveMessages))
14015  {
14016  Utilities->CallLogPop(2610);
14017  return(false); // error message given in called function
14018  }
14019  }
14020  if(AVEntry.NonRepeatingShuttleLinkHeadCode != "")
14021  {
14022  if(!CheckForDuplicateCrossReferences(3, TDEntry.HeadCode, AVEntry.NonRepeatingShuttleLinkHeadCode, GiveMessages))
14023  {
14024  Utilities->CallLogPop(2611);
14025  return(false); // error message given in called function
14026  }
14027  }
14028  }
14029  }
14030 //cross reference check
14031  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14032  {
14033  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14034  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14035  {
14036  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14037  if((AVEntry.Command != "Sns-sh") && (AVEntry.Command != "Snt-sh") && (AVEntry.Command != "Fns-sh") && (AVEntry.Command != "Frh-sh"))
14038  {
14039  if(AVEntry.OtherHeadCode != "")
14040  {
14041  if(!CheckCrossReferencesAndSetData(2, TDEntry.HeadCode, AVEntry.OtherHeadCode, false, false, GiveMessages))
14042  // false = non-shuttle
14043  {
14044  Utilities->CallLogPop(2612);
14045  return(false); // error message given in called function
14046  }
14047  }
14048  }
14049  }
14050  }
14051 
14052 // now repeat the check just for the shuttles
14053  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14054  {
14055  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14056  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14057  {
14058  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14059  if((AVEntry.Command == "Sns-sh") || (AVEntry.Command == "Snt-sh") || (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "Frh-sh"))
14060  {
14061  if(AVEntry.OtherHeadCode != "")
14062  {
14063  if(!CheckCrossReferencesAndSetData(3, TDEntry.HeadCode, AVEntry.OtherHeadCode, true, false, GiveMessages))
14064  // true = shuttle
14065  {
14066  Utilities->CallLogPop(2613);
14067  return(false); // error message given in called function
14068  }
14069  }
14070  }
14071  }
14072  }
14073 
14074 // check for proper non-repeating link cross references and that they have no repeats & that times are consistent
14075  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14076  {
14077  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14078  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14079  {
14080  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14081  if(AVEntry.NonRepeatingShuttleLinkHeadCode != "")
14082  {
14083  if(!CheckNonRepeatingShuttleLinksAndSetData(1, TDEntry.HeadCode, AVEntry.NonRepeatingShuttleLinkHeadCode, false, GiveMessages))
14084  {
14085  Utilities->CallLogPop(2614);
14086  return(false); // error message given in called function
14087  }
14088  }
14089  }
14090  }
14091 
14092 //at v2.15.0 we want to set Sns, Sfs location names, but to set Sns & Sfs first need the linked Fns & fsp/rsp to have locations set as they aren't yet,
14093 //and before v2.15.0 they were set from the corresponding Sns & Sfs locations, which in turn were set from later TimeLoc departures. At v2.15.0 it
14094 //is required to have these commands followed by Frh & Fjo, so this is why we need the linked Fns & fsp/rsp to have locations set first. Now all Fns
14095 //will have a TimeLoc before, so that can provide its location, but fsp/rsp? Must they have a TimeLoc before? No, and can't rely on starting Sfs
14096 //having the location set yet. So, new restriction, insist on an rsp/fsp having a TimeLoc before it or a located Snt, and use one of those to set the
14097 //location for the rsp/fsp and hence the linked Sfs.
14098 
14099 //NB can't allow an Sfs to be followed by another split or won't find a name, test with many existing tts then add an error to find it
14100 //Fns must be preceded by an arrival
14101 
14102 //set name for Fns from earlier location name or fail if can't find
14103  bool LocFoundFlag, FnsFoundFlag;
14104  TActionVectorEntry *AVEntryFns;
14105  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14106  {
14107  LocFoundFlag = false;
14108  FnsFoundFlag = false;
14109  for(int y = TrainDataVector.at(x).ActionVector.size() - 1; y >= 0; y--) //search backwards
14110  {
14111  if(TrainDataVector.at(x).ActionVector.at(y).Command == "Fns")
14112  {
14113  AVEntryFns = &TrainDataVector.at(x).ActionVector.at(y);
14114  FnsFoundFlag = true;
14115  continue;
14116  }
14117  if(!FnsFoundFlag)
14118  {
14119  continue;
14120  }
14121  if(TrainDataVector.at(x).ActionVector.at(y).LocationName != "")
14122  {
14123  LocFoundFlag = true;
14124  AVEntryFns->LocationName = TrainDataVector.at(x).ActionVector.at(y).LocationName;
14125 // double EVT = double(AVEntryFns->EventTime); //test
14126  break; //name found
14127  }
14128  if(TrainDataVector.at(x).ActionVector.at(y).LocationType == AtLocation) //not named yet
14129  {
14130  continue;
14131  }
14132  else
14133  {
14134  SecondPassMessage(GiveMessages, "Error in timetable - the program can't determine the location of an 'Fns' finish, it must be preceded "
14135  "by an event at the same location that has an identified location name, normally an arrival, see "
14136  + TrainDataVector.at(x).ServiceReference);
14137  TrainDataVector.clear();
14138  Utilities->CallLogPop(2596);
14139  return(false);
14140  }
14141  }
14142  if(FnsFoundFlag && !LocFoundFlag)
14143  {
14144  SecondPassMessage(GiveMessages, "Error in timetable - the program can't determine the location of an 'Fns' finish, it must be preceded "
14145  "by an event at the same location that has an identified location name, normally an arrival, see "
14146  + TrainDataVector.at(x).ServiceReference);
14147  TrainDataVector.clear();
14148  Utilities->CallLogPop(2597);
14149  return(false);
14150  }
14151  }
14152 
14153 //now set all names for Sns entries from the above, new at v2.15.0
14154  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14155  {
14156  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14157  TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
14158  // use reference so can change internals
14159  if(AVEntry0.Command == "Sns")
14160  {
14161  //new at v2.15.0. Only set location if have a forward and backward linkage at same time (these all set when SecondPassActions called).
14162  //This isn't rigorous as may have more than one, but if do then will be caught below in CheckCrossReferencesAndSetData
14163  //If fail to find location just ignore as will be caught later in CheckCrossReferencesAndSetData
14164  //note that at this stage the OtherHeadCode values are service refs, as haven't yet been changed back to headcodes until
14165  //StripExcessFromHeadCode called at end of this function
14166  //need to be the same: forward & reverse service refs, event times, commands correspond
14167 
14168  //successor checks first: /no starts, /finishes except Frh & Fjo (as of v2.15.0), /repeats, /pas, /TimeTimeLoc or /TimeLoc arr; any other cmd or TimeLoc (dep) OK
14169  if(TDEntry.ActionVector.size() < 2)
14170  {
14171  SecondPassMessage(GiveMessages, "Error in timetable - insufficient actions follwing an 'Sns' event for: " + TDEntry.HeadCode);
14172  TrainDataVector.clear();
14173  Utilities->CallLogPop(2598);
14174  return(false);
14175  }
14176  TActionVectorEntry AVEntry1 = TDEntry.ActionVector.at(1);
14177  if(!AtLocSuccessor(AVEntry1))
14178  {
14179  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sns' event is followed by an illegal event for: " + TDEntry.HeadCode +
14180  ". The event isn't valid for a stationary train.");
14181  TrainDataVector.clear();
14182  Utilities->CallLogPop(2599);
14183  return(false);
14184  }
14185  if((AVEntry1.SequenceType == StartSequence) || ((AVEntry1.SequenceType == FinishSequence) && (AVEntry1.Command != "Frh") && (AVEntry1.Command != "Fjo")) ||
14186  (AVEntry1.FormatType == Repeat))
14187  {
14188  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sns' event is followed by an illegal event for: " + TDEntry.HeadCode);
14189  TrainDataVector.clear();
14190  Utilities->CallLogPop(2600);
14191  return(false);
14192  }
14193 
14194  //now set the location and location type
14195  TDateTime SnsEventTime = AVEntry0.EventTime;
14196 // double EVT = double(SnsEventTime); //test
14197  AnsiString SnsServiceRef = TDEntry.ServiceReference;
14198  bool BreakFlag = false;
14199  for(unsigned int y = 0; y < TrainDataVector.size(); y++)
14200  {
14201  for(unsigned int z = 0; z < TrainDataVector.at(y).ActionVector.size(); z++)
14202  {
14203  if((TrainDataVector.at(y).ActionVector.at(z).Command == "Fns") && (SnsEventTime == TrainDataVector.at(y).ActionVector.at(z).EventTime) &&
14204  (TrainDataVector.at(y).HeadCode == AVEntry0.OtherHeadCode))
14205  { //forward linkage found
14206  if(TrainDataVector.at(y).ActionVector.at(z).OtherHeadCode == SnsServiceRef) //OtherHeadCode values are service refs, see above
14207  { //reverse linkage found
14208  AVEntry0.LocationName = TrainDataVector.at(y).ActionVector.at(z).LocationName;
14209  AVEntry0.LocationType = AtLocation;
14210  BreakFlag = true;
14211  break;
14212  }
14213  }
14214  }
14215  if(BreakFlag)
14216  {
14217  break;
14218  }
14219  }
14220  //test for any unnamed AtLoc entries at end of name setting
14221  }
14222  }
14223 
14224 //trap errors where rsp/fsp follows an Sfs without a TimeLoc arrival before (or unlikely to be able to set fsp/rsp/Sfs location because Sfs locs set from linked fsp/rsp events)
14225  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14226  {
14227  const TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
14228  if(AVEntry0.Command == "Sfs")
14229  {
14230  for(unsigned int y = 1; y < TrainDataVector.at(x).ActionVector.size(); y++)
14231  {
14232  if(TrainDataVector.at(x).ActionVector.at(y).LocationName != "") //must be a timeloc as only they have loc set and are AtLoc (non-AtLoc trapped above)
14233  {
14234  break;
14235  }
14236  else if((TrainDataVector.at(x).ActionVector.at(y).Command == "fsp") || (TrainDataVector.at(x).ActionVector.at(y).Command == "rsp"))
14237  {
14238  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sfs' event must be followed by a departure and arrival before another split, see " + TrainDataVector.at(x).ServiceReference);
14239  TrainDataVector.clear();
14240  Utilities->CallLogPop(2586);
14241  return(false);
14242  }
14243  }
14244  }
14245  }
14246 
14247 //now name fsp/rsp actions
14248  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14249  {
14250  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14251  {
14252  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14253  if(AVEntry.LocationName != "")
14254  {
14255  for(unsigned int z = y + 1; z < TrainDataVector.at(x).ActionVector.size(); z++)
14256  {
14257  TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(z);
14258  // use reference so can change internals where necessary
14259  if((AVEntry2.Command == "fsp") || (AVEntry2.Command == "rsp"))
14260  {
14261  AVEntry2.LocationName = AVEntry.LocationName;
14262  } //test for any unnamed AtLoc entries at end of name setting
14263  else if(AVEntry2.LocationType != AtLocation)
14264  {
14265  break;
14266  }
14267  }
14268  }
14269  }
14270  }
14271 
14272 //check that all named or give error message
14273  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14274  {
14275  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14276  {
14277  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14278  if((AVEntry.Command == "fsp") || (AVEntry.Command == "rsp"))
14279  {
14280  if(AVEntry.LocationName == "")
14281  {
14282  SecondPassMessage(GiveMessages, "Error in timetable - an 'fsp' or 'rsp' event must be preceded by an event at the same location that has an identified location name, normally an arrival, see " + TrainDataVector.at(x).ServiceReference);
14283  TrainDataVector.clear();
14284  Utilities->CallLogPop(2617);
14285  return(false);
14286  }
14287  }
14288  }
14289  }
14290 
14291 //now set all Sfs entries from the above
14292  for(unsigned int x = 0; x < TrainDataVector.size(); x++) //at v2.15.0 set Sfs & Sns locations from corresponding fsp/rsp & Fns entries, shuttles ok as they are
14293  {
14294  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14295  TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
14296  // use reference so can change internals
14297  if(AVEntry0.Command == "Sfs")
14298  {
14299  //new at v2.15.0. Only set location if have a forward and backward linkage at same time (these all set when SecondPassActions called). This isn't
14300  //rigorous as may have more than one, but if do then will be caught below in CheckCrossReferencesAndSetData
14301  //If fail to find location just ignore as will be caught later in CheckCrossReferencesAndSetData
14302  //note that at this stage the OtherHeadCode values are service refs, as haven't yet been changed back to headcodes until StripExcessFromHeadCode called
14303  //at end of this function
14304  //need to be the same: forward & reverse service refs, event times, commands correspond
14305 
14306  //successor checks first: /no starts, /finishes except Frh & Fjo (as of v2.15.0), /repeats, /pas, /TimeTimeLoc or /TimeLoc arr; any other cmd or TimeLoc (dep) OK
14307  if(TDEntry.ActionVector.size() < 2)
14308  {
14309  SecondPassMessage(GiveMessages, "Error in timetable - insufficient actions follwing an 'Sfs' event for: " + TDEntry.HeadCode);
14310  TrainDataVector.clear();
14311  Utilities->CallLogPop(2587);
14312  return(false);
14313  }
14314  TActionVectorEntry AVEntry1 = TDEntry.ActionVector.at(1);
14315  if(!AtLocSuccessor(AVEntry1))
14316  {
14317  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sfs' event is followed by an illegal event for: " + TDEntry.HeadCode +
14318  ". The event isn't valid for a stationary train.");
14319  TrainDataVector.clear();
14320  Utilities->CallLogPop(2588);
14321  return(false);
14322  }
14323  if((AVEntry1.SequenceType == StartSequence) || ((AVEntry1.SequenceType == FinishSequence) && (AVEntry1.Command != "Frh") && (AVEntry1.Command != "Fjo")) ||
14324  (AVEntry1.FormatType == Repeat))
14325  {
14326  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sfs' event is followed by an illegal event for: " + TDEntry.HeadCode);
14327  TrainDataVector.clear();
14328  Utilities->CallLogPop(2589);
14329  return(false);
14330  }
14331 
14332  //now set the location and location type
14333  TDateTime SfsEventTime = AVEntry0.EventTime;
14334  AnsiString SfsServiceRef = TDEntry.ServiceReference;
14335  bool BreakFlag = false;
14336  for(unsigned int y = 0; y < TrainDataVector.size(); y++)
14337  {
14338  for(unsigned int z = 0; z < TrainDataVector.at(y).ActionVector.size(); z++)
14339  {
14340  if(((TrainDataVector.at(y).ActionVector.at(z).Command == "fsp") || (TrainDataVector.at(y).ActionVector.at(z).Command == "rsp")) &&
14341  (SfsEventTime == TrainDataVector.at(y).ActionVector.at(z).EventTime) && (TrainDataVector.at(y).HeadCode == AVEntry0.OtherHeadCode))
14342  { //forward linkage found
14343  if(TrainDataVector.at(y).ActionVector.at(z).OtherHeadCode == SfsServiceRef) //OtherHeadCode values are service refs, see above
14344  { //reverse linkage found
14345  AVEntry0.LocationName = TrainDataVector.at(y).ActionVector.at(z).LocationName;
14346  AVEntry0.LocationType = AtLocation;
14347  BreakFlag = true;
14348  break;
14349  }
14350  }
14351  }
14352  if(BreakFlag)
14353  {
14354  break;
14355  }
14356  } //test for any unnamed AtLoc entries at end of name setting
14357  }
14358  }
14359 
14360  // set all cmd locations based on earlier location name in TimeLoc arrival or Sfs/Sns/Sns-sh/Sns-fsh/located Snt/Snt-sh locations
14361  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14362  {
14363  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14364  {
14365  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14366  if(AVEntry.LocationName != "")
14367  {
14368  for(unsigned int z = y + 1; z < TrainDataVector.at(x).ActionVector.size(); z++)
14369  {
14370  TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(z);
14371  // use reference so can change internals where necessary
14372  if((AVEntry2.Command != "") && (AVEntry2.LocationType == AtLocation))
14373  {
14374  AVEntry2.LocationName = AVEntry.LocationName;
14375  } //test for any unnamed AtLoc entries at end of name setting
14376  else
14377  {
14378  break;
14379  }
14380  }
14381  }
14382  }
14383  }
14384  // all location names should now be set
14385 
14386 //now test for any unnamed AtLoc entries where Command != "" and give message if find any - shouldn't find any if above checks comprehensive
14387  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14388  {
14389  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14390  {
14391  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14392  if((AVEntry.LocationType == AtLocation) && (AVEntry.LocationName == "") && (AVEntry.Command != ""))
14393  {
14394  if((AVEntry.Command == "jbo") || (AVEntry.Command == "fsp") || (AVEntry.Command == "rsp") || (AVEntry.Command == "cdt") || (AVEntry.Command == "dsc") || (AVEntry.Command == "cms"))
14395  {
14396  SecondPassMessage(GiveMessages, "Error in timetable - '" + AVEntry.Command + "' must be preceded by an event at the same location that has an identified location name, normally an arrival, see " + TrainDataVector.at(x).ServiceReference);
14397  TrainDataVector.clear();
14398  Utilities->CallLogPop(2619);
14399  return(false);
14400  }
14401  else if((AVEntry.Command == "Sns") || (AVEntry.Command == "Sfs"))
14402  {
14403  SecondPassMessage(GiveMessages, "Error in timetable - the location of the '" + AVEntry.Command + "' event in service " + TrainDataVector.at(x).ServiceReference + " can't be identified. " +
14404  "Please make sure that the finish event of the service that links to this event is preceded by an "
14405  "event at the same location that has an identified location name, normally an arrival.");
14406  TrainDataVector.clear();
14407  Utilities->CallLogPop(2620);
14408  return(false);
14409  }
14410  else if(AVEntry.Command == "Snt-sh")
14411  {
14412  SecondPassMessage(GiveMessages, "Error in timetable - the location of the '" + AVEntry.Command + "' event in service " + TrainDataVector.at(x).ServiceReference + " can't be identified. " +
14413  "Please make sure that the service starts with zero speed and is at a named location.");
14414  TrainDataVector.clear();
14415  Utilities->CallLogPop(2623);
14416  return(false);
14417  }
14418  else if((AVEntry.Command == "Sns-fsh") || (AVEntry.Command == "Sns-sh"))
14419  {
14420  SecondPassMessage(GiveMessages, "Error in timetable - the location of the '" + AVEntry.Command + "' event in service " + TrainDataVector.at(x).ServiceReference + " can't be identified. " +
14421  "Please make sure that the event is followed (not necessarily immediately) by a departure.");
14422  TrainDataVector.clear();
14423  Utilities->CallLogPop(2622);
14424  return(false);
14425  }
14426  else if((AVEntry.Command == "Frh") || (AVEntry.Command == "Fns") || (AVEntry.Command == "Fjo") || (AVEntry.Command == "Frh-sh") || (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "F-nshs"))
14427  {
14428  SecondPassMessage(GiveMessages, "Error in timetable - '" + AVEntry.Command + "must be preceded by an event at the same location that has an identified location name, normally an arrival, see " + TrainDataVector.at(x).ServiceReference);
14429  TrainDataVector.clear();
14430  Utilities->CallLogPop(2621);
14431  return(false);
14432  }
14433  }
14434  }
14435  }
14436 
14437  // check remaining successor validity except for TimeLoc arr & dep since those times not set yet
14438  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14439  {
14440  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14441  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14442  {
14443  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14444  if((AVEntry.SequenceType == FinishSequence) && (AVEntry.Command != "F-nshs"))
14445  {
14446  if(y < (TrainDataVector.at(x).ActionVector.size() - 1))
14447  // i.e at least one more, must be a repeat
14448  {
14449  if(TrainDataVector.at(x).ActionVector.at(y + 1).FormatType != Repeat)
14450  {
14451  SecondPassMessage(GiveMessages, "Error in timetable - only a repeat can follow a finish event for: " + TDEntry.HeadCode);
14452  TrainDataVector.clear();
14453  Utilities->CallLogPop(798);
14454  return(false);
14455  }
14456  }
14457  }
14458  if(AVEntry.Command == "F-nshs")
14459  {
14460  if(y != (TrainDataVector.at(x).ActionVector.size() - 1))
14461  // i.e has to be the last
14462  {
14463  SecondPassMessage(GiveMessages, "Error in timetable - F-nshs (shuttle link) must be the last event for: " + TDEntry.HeadCode);
14464  TrainDataVector.clear();
14465  Utilities->CallLogPop(1049);
14466  return(false);
14467  }
14468  }
14469  if(AVEntry.Command == "pas")
14470  {
14471  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14472  {
14473  SecondPassMessage(GiveMessages, "Error in timetable - a 'pas' can't be the last event for: " + TDEntry.HeadCode);
14474  TrainDataVector.clear();
14475  Utilities->CallLogPop(1518);
14476  return(false);
14477  }
14478  }
14479  if(AVEntry.Command == "jbo")
14480  {
14481  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14482  {
14483  SecondPassMessage(GiveMessages, "Error in timetable - a 'jbo' can't be the last event for: " + TDEntry.HeadCode);
14484  TrainDataVector.clear();
14485  Utilities->CallLogPop(800);
14486  return(false);
14487  }
14488  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14489  if(!AtLocSuccessor(AVEntry2))
14490  {
14491  SecondPassMessage(GiveMessages, "Error in timetable - a jbo event is followed by an illegal event for: " + TDEntry.HeadCode +
14492  ". The event isn't valid for a stationary train.");
14493  TrainDataVector.clear();
14494  Utilities->CallLogPop(801);
14495  return(false);
14496  }
14497  }
14498  if((AVEntry.Command == "fsp") || (AVEntry.Command == "rsp"))
14499  {
14500  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14501  {
14502  SecondPassMessage(GiveMessages, "Error in timetable - a train split can't be the last event for: " + TDEntry.HeadCode);
14503  TrainDataVector.clear();
14504  Utilities->CallLogPop(802);
14505  return(false);
14506  }
14507  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14508  if(!AtLocSuccessor(AVEntry2))
14509  {
14510  SecondPassMessage(GiveMessages, "Error in timetable - a train split is followed by an illegal event for: " + TDEntry.HeadCode +
14511  ". The event isn't valid for a stationary train.");
14512  TrainDataVector.clear();
14513  Utilities->CallLogPop(803);
14514  return(false);
14515  }
14516  }
14517  if(AVEntry.Command == "cdt")
14518  {
14519  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14520  {
14521  SecondPassMessage(GiveMessages, "Error in timetable - a 'cdt' can't be the last event for: " + TDEntry.HeadCode);
14522  TrainDataVector.clear();
14523  Utilities->CallLogPop(804);
14524  return(false);
14525  }
14526  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14527  if(!AtLocSuccessor(AVEntry2))
14528  {
14529  SecondPassMessage(GiveMessages, "Error in timetable - a 'cdt' is followed by an illegal event for: " + TDEntry.HeadCode +
14530  ". The event isn't valid for a stationary train.");
14531  TrainDataVector.clear();
14532  Utilities->CallLogPop(805);
14533  return(false);
14534  }
14535  }
14536  if(AVEntry.Command == "dsc")
14537  {
14538  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14539  {
14540  SecondPassMessage(GiveMessages, "Error in timetable - a 'dsc' can't be the last event for: " + TDEntry.HeadCode);
14541  TrainDataVector.clear();
14542  Utilities->CallLogPop(2602);
14543  return(false);
14544  }
14545  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14546  if(!AtLocSuccessor(AVEntry2))
14547  {
14548  SecondPassMessage(GiveMessages, "Error in timetable - a 'dsc' is followed by an illegal event for: " + TDEntry.HeadCode +
14549  ". The event isn't valid for a stationary train.");
14550  TrainDataVector.clear();
14551  Utilities->CallLogPop(2603);
14552  return(false);
14553  }
14554  }
14555  if(AVEntry.Command == "cms")
14556  {
14557  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14558  {
14559  SecondPassMessage(GiveMessages, "Error in timetable - a 'cms' can't be the last event for: " + TDEntry.HeadCode);
14560  TrainDataVector.clear();
14561  Utilities->CallLogPop(2711);
14562  return(false);
14563  }
14564  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14565  if(!AtLocSuccessor(AVEntry2))
14566  {
14567  SecondPassMessage(GiveMessages, "Error in timetable - a 'cms' is followed by an illegal event for: " + TDEntry.HeadCode +
14568  ". The event isn't valid for a stationary train.");
14569  TrainDataVector.clear();
14570  Utilities->CallLogPop(2712);
14571  return(false);
14572  }
14573  }
14574  if(AVEntry.FormatType == TimeTimeLoc)
14575  {
14576  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14577  {
14578  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival and departure can't be the last event for: " + TDEntry.HeadCode);
14579  TrainDataVector.clear();
14580  Utilities->CallLogPop(806);
14581  return(false);
14582  }
14583  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14584  if(!MovingSuccessor(AVEntry2))
14585  {
14586  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival and departure is followed by an illegal event for: " +
14587  TDEntry.HeadCode + ". The event isn't valid for a moving train.");
14588  TrainDataVector.clear();
14589  Utilities->CallLogPop(807);
14590  return(false);
14591  }
14592  }
14593  if(AVEntry.FormatType == PassTime)
14594  {
14595  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14596  {
14597  SecondPassMessage(GiveMessages, "Error in timetable - a pass time can't be the last event for: " + TDEntry.HeadCode);
14598  TrainDataVector.clear();
14599  Utilities->CallLogPop(1530);
14600  return(false);
14601  }
14602  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14603  if(!MovingSuccessor(AVEntry2))
14604  {
14605  SecondPassMessage(GiveMessages, "Error in timetable - a pass time is followed by an illegal event for: " + TDEntry.HeadCode +
14606  ". The event isn't valid for a moving train.");
14607  TrainDataVector.clear();
14608  Utilities->CallLogPop(1531);
14609  return(false);
14610  }
14611  }
14612  if(AVEntry.FormatType == Repeat)
14613  {
14614  if(y != (TrainDataVector.at(x).ActionVector.size() - 1))
14615  {
14616  SecondPassMessage(GiveMessages, "Error in timetable - a repeat is not the last item for: " + TDEntry.HeadCode);
14617  TrainDataVector.clear();
14618  Utilities->CallLogPop(808);
14619  return(false);
14620  }
14621  }
14622  }
14623  }
14624 
14625  // set arrival & departure times for TimeLocs & set their EventTimes to -1
14626  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14627  {
14628  bool LastEntryIsAnArrival = false;
14629  const TTrainDataEntry & TDEntry = TrainDataVector.at(x);
14630  // first deal with unlocated Snt entries - so next entry (TimeLoc or TimeTimeLoc) is an arrival, all else stopped so the next TimeLoc is a departure
14631  const TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
14632  if((AVEntry0.Command == "Snt") && (AVEntry0.LocationType == EnRoute))
14633  // StartSpeed may or may not be 0, but train will move forwards (if capable of doing so), & next TimeLoc will be an arrival, whether or not after one or more TimeTimeLocs
14634  {
14635  LastEntryIsAnArrival = false;
14636  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14637  {
14638  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14639  if(AVEntry.FormatType == TimeLoc)
14640  {
14641  if((AVEntry.ArrivalTime > TDateTime(-1)) || (AVEntry.DepartureTime > TDateTime(-1)) || (AVEntry.EventTime == TDateTime(-1)))
14642  {
14643  throw Exception("Timetable error, TimeLoc times not as initially set for " + TDEntry.HeadCode);
14644  }
14645  if(LastEntryIsAnArrival)
14646  {
14647  AVEntry.DepartureTime = AVEntry.EventTime;
14648  AVEntry.EventTime = TDateTime(-1);
14649  LastEntryIsAnArrival = false;
14650  }
14651  else // last entry a departure
14652  {
14653  AVEntry.ArrivalTime = AVEntry.EventTime;
14654  AVEntry.EventTime = TDateTime(-1);
14655  LastEntryIsAnArrival = true;
14656  }
14657  }
14658  }
14659  }
14660  else // all others stopped at beginning
14661  {
14662  LastEntryIsAnArrival = true;
14663  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14664  {
14665  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14666  if(AVEntry.FormatType == TimeLoc)
14667  {
14668  if((AVEntry.ArrivalTime > TDateTime(-1)) || (AVEntry.DepartureTime > TDateTime(-1)) || (AVEntry.EventTime == TDateTime(-1)))
14669  {
14670  throw Exception("Timetable error, TimeLoc times not as initially set for " + TDEntry.HeadCode);
14671  }
14672  if(LastEntryIsAnArrival)
14673  {
14674  AVEntry.DepartureTime = AVEntry.EventTime;
14675  AVEntry.EventTime = TDateTime(-1);
14676  LastEntryIsAnArrival = false;
14677  }
14678  else // last entry a departure
14679  {
14680  AVEntry.ArrivalTime = AVEntry.EventTime;
14681  AVEntry.EventTime = TDateTime(-1);
14682  LastEntryIsAnArrival = true;
14683  }
14684  }
14685  }
14686  }
14687  }
14688  // perform remaining successor checks for TimeLocs
14689  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14690  {
14691  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14692  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14693  {
14694  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14695  if((AVEntry.FormatType == TimeLoc) && (AVEntry.ArrivalTime >= TDateTime(0))) // arrival
14696  // TimeLoc (arr) -> No starts, repeats, Fer or TimeTimeLoc; TimeLoc (dep) or any other OK
14697  {
14698  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14699  {
14700  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival can't be the last event for: " + TDEntry.HeadCode);
14701  TrainDataVector.clear();
14702  Utilities->CallLogPop(809);
14703  return(false);
14704  }
14705  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14706  if(!AtLocSuccessor(AVEntry2))
14707  {
14708  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival is followed by an illegal event for: " + TDEntry.HeadCode +
14709  ". The event isn't valid for a stationary train.");
14710  TrainDataVector.clear();
14711  Utilities->CallLogPop(810);
14712  return(false);
14713  }
14714  }
14715  if((AVEntry.FormatType == TimeLoc) && (AVEntry.DepartureTime >= TDateTime(0))) // departure
14716  // TimeLoc (dep) -> Fer, TimeLoc (arr), TimeTimeLoc, (new) pas OK, no others
14717  {
14718  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14719  {
14720  SecondPassMessage(GiveMessages, "Error in timetable - a timed departure can't be the last event for: " + TDEntry.HeadCode);
14721  TrainDataVector.clear();
14722  Utilities->CallLogPop(811);
14723  return(false);
14724  }
14725  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14726  if(!MovingSuccessor(AVEntry2))
14727  {
14728  SecondPassMessage(GiveMessages, "Error in timetable - a timed departure is followed by an illegal event for: " + TDEntry.HeadCode +
14729  ". The event isn't valid for a moving train.");
14730  TrainDataVector.clear();
14731  Utilities->CallLogPop(812);
14732  return(false);
14733  }
14734  }
14735  }
14736  }
14737 
14738  // check all TimeLocs have either Arr or Dep time set and EventTime == -1, all Cmds have EventTime set & Arr & Dep times == -1,
14739  // & repeats have no times set
14740  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14741  {
14742  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14743  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14744  {
14745  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14746  if(AVEntry.FormatType == TimeLoc)
14747  {
14748  if(AVEntry.EventTime != TDateTime(-1))
14749  {
14750  throw Exception("Timetable error, TimeLoc event has EventTime not -1 for " + TDEntry.HeadCode);
14751  }
14752  if((AVEntry.ArrivalTime == TDateTime(-1)) && (AVEntry.DepartureTime == TDateTime(-1)))
14753  {
14754  throw Exception("Timetable error, TimeLoc event has neither arrival nor departure time set for " + TDEntry.HeadCode);
14755  }
14756  }
14757  if(AVEntry.FormatType == TimeTimeLoc)
14758  {
14759  if(AVEntry.EventTime != TDateTime(-1))
14760  {
14761  throw Exception("Timetable error, TimeTimeLoc event has EventTime not -1 for " + TDEntry.HeadCode);
14762  }
14763  if((AVEntry.ArrivalTime == TDateTime(-1)) || (AVEntry.DepartureTime == TDateTime(-1)))
14764  {
14765  throw Exception("Timetable error, TimeTimeLoc event has either arrival or departure time not set for " + TDEntry.HeadCode);
14766  }
14767  }
14768  if((AVEntry.FormatType == TimeCmd) || (AVEntry.FormatType == TimeCmdHeadCode) || (AVEntry.FormatType == StartNew) ||
14769  (AVEntry.FormatType == SNTShuttle) || (AVEntry.FormatType == SNSShuttle) || (AVEntry.FormatType == FNSNonRepeatToShuttle) ||
14770  (AVEntry.FormatType == FSHNewService) || (AVEntry.FormatType == PassTime))
14771  {
14772  if(AVEntry.EventTime == TDateTime(-1))
14773  {
14774  throw Exception("Timetable error, Cmd or PassTime event has EventTime not set for " + TDEntry.HeadCode);
14775  }
14776  if((AVEntry.ArrivalTime != TDateTime(-1)) || (AVEntry.DepartureTime != TDateTime(-1)))
14777  {
14778  throw Exception("Timetable error, Cmd or PassTime event has either arrival or departure time set for " + TDEntry.HeadCode);
14779  }
14780  }
14781  if(AVEntry.FormatType == Repeat)
14782  {
14783  if((AVEntry.EventTime != TDateTime(-1)) || (AVEntry.ArrivalTime != TDateTime(-1)) || (AVEntry.DepartureTime != TDateTime(-1)))
14784  {
14785  throw Exception("Timetable error, Repeat event has a time set for " + TDEntry.HeadCode);
14786  }
14787  }
14788  }
14789  }
14790 
14791  // check times stay same or increase, note that can have time of 0 if include midnight
14792  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14793  {
14794  TDateTime CurrentTime = TTClockTime; // the timetable start time
14795  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14796  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14797  {
14798  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14799  if(AVEntry.FormatType == Repeat)
14800  {
14801  break;
14802  }
14803  if(AVEntry.FormatType == FinRemHere)
14804  {
14805  break;
14806  }
14807  if(AVEntry.FormatType == TimeTimeLoc)
14808  {
14809  if(AVEntry.DepartureTime < AVEntry.ArrivalTime)
14810  {
14811  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival and departure has a later arrival than departure time for: " +
14812  TDEntry.HeadCode);
14813  TrainDataVector.clear();
14814  Utilities->CallLogPop(813);
14815  return(false);
14816  }
14817  if(AVEntry.ArrivalTime < CurrentTime)
14818  {
14819  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival and departure has too early an arrival time for: " +
14820  TDEntry.HeadCode);
14821  TrainDataVector.clear();
14822  Utilities->CallLogPop(814);
14823  return(false);
14824  }
14825  CurrentTime = AVEntry.DepartureTime;
14826  continue;
14827  }
14828  if(AVEntry.FormatType == TimeLoc)
14829  {
14830  if(AVEntry.ArrivalTime >= TDateTime(0))
14831  {
14832  if(AVEntry.ArrivalTime < CurrentTime)
14833  {
14834  SecondPassMessage(GiveMessages, "Error in timetable - a timed location event has a time that is too early for: " + TDEntry.HeadCode);
14835  TrainDataVector.clear();
14836  Utilities->CallLogPop(815);
14837  return(false);
14838  }
14839  CurrentTime = AVEntry.ArrivalTime;
14840  }
14841  else
14842  {
14843  if(AVEntry.DepartureTime < CurrentTime)
14844  // both may be 0 legitimately so must allow for this
14845  {
14846  SecondPassMessage(GiveMessages, "Error in timetable - a timed location event has a time that is too early for: " + TDEntry.HeadCode);
14847  TrainDataVector.clear();
14848  Utilities->CallLogPop(816);
14849  return(false);
14850  }
14851  CurrentTime = AVEntry.DepartureTime;
14852  }
14853  continue;
14854  }
14855  if(AVEntry.EventTime < CurrentTime)
14856  // all others have EventTime set
14857  {
14858  SecondPassMessage(GiveMessages, "Error in timetable - a train event has a time that is set too early for: " + TDEntry.HeadCode +
14859  ", may be before timetable start time");
14860  TrainDataVector.clear();
14861  Utilities->CallLogPop(835);
14862  return(false);
14863  }
14864  CurrentTime = AVEntry.EventTime;
14865  continue;
14866  }
14867  }
14868 
14869  // check locations consistent
14870  AnsiString LastLocationName = "";
14871 
14872  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14873  {
14874  bool LastEntryIsAnArrival = false;
14875  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14876  // first deal with moving Snt entries (all else stopped)
14877  if((TrainDataVector.at(x).ActionVector.at(0).Command == "Snt") && (TrainDataVector.at(x).ActionVector.at(0).LocationType == EnRoute))
14878  {
14879  LastEntryIsAnArrival = false;
14880  LastLocationName = TrainDataVector.at(x).ActionVector.at(0).LocationName; // should be ""
14881  if(LastLocationName != "")
14882  {
14883  throw Exception("Timetable error, moving Snt event has LocationName set for " + TDEntry.HeadCode);
14884  }
14885  for(unsigned int y = 1; y < TrainDataVector.at(x).ActionVector.size();
14886  y++) // note that immediate successor to a moving Snt can only be a Moving type
14887  {
14888  // if it's a SignallerControl entry then the condition isn't met
14889  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14890  if(AVEntry.FormatType == Repeat)
14891  {
14892  break; // repeat = reached end (+allows repeat after signaller controlled entry)
14893  }
14894  else if((AVEntry.FormatType == TimeCmdHeadCode) || (AVEntry.FormatType == FNSNonRepeatToShuttle))
14895  {
14896  if(AVEntry.LocationName != LastLocationName)
14897  {
14898  SecondPassMessage(GiveMessages, "Error in timetable - a location event is inconsistent for: " + TDEntry.HeadCode + " && " +
14899  AVEntry.Command);
14900  TrainDataVector.clear();
14901  Utilities->CallLogPop(823);
14902  return(false);
14903  }
14904  }
14905  else if((AVEntry.FormatType == TimeCmd) || (AVEntry.FormatType == TimeCmdDescription) || (AVEntry.FormatType == TimeCmdMaxSpeed))
14906  // cdt is the only TimeCmd, dsc is the only TimeCmdDescription & cms is the only TimeCmdMaxSpeed
14907  {
14908  if(AVEntry.LocationName != LastLocationName)
14909  {
14910  SecondPassMessage(GiveMessages, "Error in timetable - a location event is inconsistent for: " + TDEntry.HeadCode + " && " +
14911  AVEntry.Command);
14912  TrainDataVector.clear();
14913  Utilities->CallLogPop(824);
14914  return(false);
14915  }
14916  }
14917  else if(AVEntry.FormatType == TimeTimeLoc)
14918  {
14919  if((AVEntry.LocationName == LastLocationName) && TTEditPanelVisible) //changed at v2.6.0 to allow loops & consecutive same locs
14920  // last entry must be a departure or would have failed earlier
14921  {
14922  TwoLocationList.insert(TwoLocationList.end(), TDEntry.ServiceReference); //added at v2.9.1
14923  TwoLocationFlag = true;
14924 // ShowMessage("Two or more locations are the same without a change of direction between them. Please correct if this is an error.\n\nThis warning will not be shown again.");
14925 // TwoOrMoreLocationsWarningGiven = true;
14926  }
14927  LastLocationName = AVEntry.LocationName;
14928  LastEntryIsAnArrival = false;
14929  }
14930  else if(AVEntry.FormatType == TimeLoc)
14931  {
14932  if(LastEntryIsAnArrival && (AVEntry.LocationName != LastLocationName))
14933  {
14934  SecondPassMessage(GiveMessages,
14935  "Error in timetable - a location event for a timed departure is different from the arrival location for: " + TDEntry.HeadCode);
14936  TrainDataVector.clear();
14937  Utilities->CallLogPop(826);
14938  return(false);
14939  }
14940  else if(!LastEntryIsAnArrival && (AVEntry.LocationName == LastLocationName))
14941  {
14942  SecondPassMessage(GiveMessages,
14943  "Error in timetable - a location event for a timed arrival is the same as the earlier departure location for: " + TDEntry.HeadCode);
14944  TrainDataVector.clear();
14945  Utilities->CallLogPop(827);
14946  return(false);
14947  }
14948  LastLocationName = AVEntry.LocationName;
14949  LastEntryIsAnArrival = !LastEntryIsAnArrival;
14950  }
14951  }
14952  }
14953  else // all stationary starting entries
14954  {
14955  LastEntryIsAnArrival = true;
14956  LastLocationName = TrainDataVector.at(x).ActionVector.at(0).LocationName;
14957  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14958  {
14959  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14960  if(AVEntry.FormatType == Repeat)
14961  {
14962  break;
14963  }
14964  else if((AVEntry.FormatType == TimeCmdHeadCode) || (AVEntry.FormatType == FNSNonRepeatToShuttle))
14965  // no need to add anything for shuttle starts since they are at loc (0) anyway
14966  {
14967  if(AVEntry.LocationName != LastLocationName)
14968  {
14969  SecondPassMessage(GiveMessages, "Error in timetable - a location event is inconsistent for: " + TDEntry.HeadCode + " && " +
14970  AVEntry.Command);
14971  TrainDataVector.clear();
14972  Utilities->CallLogPop(828);
14973  return(false);
14974  }
14975  }
14976  else if(AVEntry.FormatType == TimeCmd)
14977  // cdt is the only TimeCmd
14978  {
14979  if(AVEntry.LocationName != LastLocationName)
14980  {
14981  SecondPassMessage(GiveMessages, "Error in timetable - a location event is inconsistent for: " + TDEntry.HeadCode + " && " +
14982  AVEntry.Command);
14983  TrainDataVector.clear();
14984  Utilities->CallLogPop(829);
14985  return(false);
14986  }
14987  }
14988  else if(AVEntry.FormatType == TimeTimeLoc)
14989  {
14990  if((AVEntry.LocationName == LastLocationName) && TTEditPanelVisible) //changed at v2.6.0 to allow loops & consecutive same locs
14991  // last entry must be a departure or would have failed earlier
14992  {
14993  TwoLocationList.insert(TwoLocationList.end(), TDEntry.ServiceReference); //added at v2.9.1
14994  TwoLocationFlag = true;
14995 // ShowMessage("Two or more locations are the same without a change of direction between them. Please correct if this is an error.\n\nThis warning will not be shown again.");
14996 // TwoOrMoreLocationsWarningGiven = true;
14997  }
14998  LastLocationName = AVEntry.LocationName;
14999  LastEntryIsAnArrival = false;
15000  }
15001  else if(AVEntry.FormatType == TimeLoc)
15002  {
15003  if(LastEntryIsAnArrival && (AVEntry.LocationName != LastLocationName))
15004  {
15005  SecondPassMessage(GiveMessages,
15006  "Error in timetable - a location event for a timed departure is different from the arrival location for: " + TDEntry.HeadCode);
15007  TrainDataVector.clear();
15008  Utilities->CallLogPop(831);
15009  return(false);
15010  }
15011  if(!LastEntryIsAnArrival && (AVEntry.LocationName == LastLocationName) && !TwoOrMoreLocationsWarningGiven)
15012  {
15013  SecondPassMessage(GiveMessages,
15014  "A location event for a timed arrival is the same as the earlier departure location for: " + TDEntry.HeadCode + ". Please correct if this is an error.\n\nThis warning will not be shown again.");
15016 // TrainDataVector.clear();
15017 // Utilities->CallLogPop(832);
15018 // return false;
15019  }
15020  LastLocationName = AVEntry.LocationName;
15021  LastEntryIsAnArrival = !LastEntryIsAnArrival;
15022  }
15023  }
15024  }
15025  }
15026 
15027  // Check same location doesn't appear twice before a cdt except for separate arr & dep TimeLocs (just a potential error warning given in v2.6.0)
15028  // i.e. same location can appear in any number of consecutive entries but once changed couldn't repeat before a direction change prior to v2.6.0
15029  AnsiString LocationNameToBeChecked = "";
15030 
15031  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15032  {
15033  const TTrainDataEntry & TDEntry = TrainDataVector.at(x);
15034  unsigned int y = 0;
15035  const TActionVectorEntry &AVEntry0 = TDEntry.ActionVector.at(0);
15036  // first discard unlocated Snt entries as they don't have location name set
15037  if((AVEntry0.Command == "Snt") && (AVEntry0.LocationType == EnRoute))
15038  {
15039  y = 1;
15040  }
15041  while(y < TDEntry.ActionVector.size())
15042  // need to check each location name separately in turn, skipped for SignallerControl entries
15043  {
15044  if((TDEntry.ActionVector.at(y).Command == "Fer") || (TDEntry.ActionVector.at(y).FormatType == Repeat))
15045  {
15046  break; // out of the 'while' loop since have reached the end & 'Fer' & 'Repeat' have no location name set
15047  }
15048  LocationNameToBeChecked = TDEntry.ActionVector.at(y).LocationName;
15049  for(unsigned int z = y; z < TDEntry.ActionVector.size(); z++)
15050  {
15051  const TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(z);
15052  if((AVEntry.Command == "Fer") || (AVEntry.FormatType == Repeat))
15053  {
15054  break; // out of the 'z' loop since have reached the end & 'Fer' & 'Repeat' have no location name set
15055  }
15056  if(AVEntry.Command == "cdt")
15057  {
15058  break; // out of the 'z' loop since the check is only valid up to a change of direction
15059  }
15060  if(AVEntry.LocationName == LocationNameToBeChecked)
15061  {
15062  continue; // keep going while name same
15063  }
15064  if(AVEntry.LocationName != LocationNameToBeChecked)
15065  // if name different check forwards to see if repeats
15066  {
15067  for(unsigned int a = z; a < TDEntry.ActionVector.size(); a++)
15068  {
15069  if(TDEntry.ActionVector.at(a).Command == "cdt")
15070  {
15071  break; // out of the 'a' & 'z' loops since the check is only valid up to a change of direction
15072  }
15073  if((TDEntry.ActionVector.at(a).LocationName == LocationNameToBeChecked) && TTEditPanelVisible) //changed at v2.6.0 to allow loops & consecutive same locs
15074  {
15075  TwoLocationList.insert(TwoLocationList.end(), TDEntry.ServiceReference); //added at v2.9.1
15076  TwoLocationFlag = true;
15077 // ShowMessage("Two or more locations are the same without a change of direction between them. Please correct if this is an error.\n\nThis warning will not be shown again.");
15078 // TwoOrMoreLocationsWarningGiven = true;
15079  }
15080  }
15081  break; // out of the 'z' loop since have checked 'a' as far as need to
15082  }
15083  }
15084  y++;
15085  }
15086  }
15087  if(TwoLocationFlag) //messages for this moved to InterfaceUnit - separate box listing potential errors and asking user to check
15088  {
15089  TwoLocationList.sort(); //need to sort first in alphabetical order to ensure all duplictes removed
15090  TwoLocationList.unique(); //remove duplicates
15091  }
15092 
15093  // check all locations except unlocated 'Snt' & 'Fer' have LocationName set
15094  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15095  {
15096  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15097  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15098  {
15099  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15100  if((AVEntry.LocationName == "") && (AVEntry.Command != "Snt") && (AVEntry.Command != "Fer") && (AVEntry.FormatType != Repeat))
15101  {
15102  throw Exception("Error, non- 'Snt', 'Fer' or Repeat event doesn't have a location name set for " + TDEntry.HeadCode);
15103  }
15104  AnsiString LocName = "";
15105  // dummy, only used so can call IsSNTEntryLocated
15106  if((AVEntry.Command == "Snt") && (IsSNTEntryLocated(1, TrainDataVector.at(x), LocName)))
15107  {
15108  if(AVEntry.LocationName == "")
15109  {
15110  throw Exception("Error, 'Snt' event at a stop location doesn't have a location name set for " + TDEntry.HeadCode);
15111  }
15112  }
15113  if((AVEntry.Command == "Snt") && !(IsSNTEntryLocated(2, TrainDataVector.at(x), LocName)))
15114  {
15115  if(AVEntry.LocationName != "")
15116  {
15117  throw Exception("Error, 'Snt' unlocated event has a location name set for " + TDEntry.HeadCode);
15118  }
15119  }
15120  }
15121  }
15122 
15123 /* Check a split to 'x' doesn't again split to 'x' (anywhere, not just for one train, since headcodes can be duplicated)
15124  Check each joined by train not joined by same train again (anywhere, not just for one train, since headcodes can be duplicated)
15125  Check each change of headcode not repeated anywhere else (anywhere, not just for one train, since headcodes can be duplicated)
15126 
15127  i.e. check everywhere where there is an 'OtherHeadCode' that it matches once only with its reference (both ways) + set
15128  the OtherHeadCodeStartingEntryPtr pointers where appropriate + train information for splits & new services
15129 
15130  BUT need to separate the shuttles from non-shuttles, because can have two trains reference each other in both forms,
15131  eg 2F44 Sns-sh ends in Fns to 2F45, & Sns 2F45 ends in Fns-sh to 2F44. Here 2F45 is the 'OtherHeadCode' for both
15132  Sns-sh & Fns in train 2F44, & 2F44 is the 'OtherHeadCode' for both Sns & Fns-sh in train 2F45.
15133 */
15134  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // new test to ensure no duplicate links at all, other checks ensure none for shuttles,
15135  {
15136  // non-shuttles & non-repeating links separately, but don't check that there isn't a
15137  // duplicate between a non-repeating shuttle and another - leave original tests in as
15138  // these also set the pointers
15139  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15140  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15141  {
15142  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15143  if(AVEntry.OtherHeadCode != "")
15144  {
15145  if(!CheckForDuplicateCrossReferences(0, TDEntry.HeadCode, AVEntry.OtherHeadCode, GiveMessages))
15146  {
15147  Utilities->CallLogPop(1584);
15148  return(false); // error message given in called function
15149  }
15150  }
15151  if(AVEntry.NonRepeatingShuttleLinkHeadCode != "")
15152  {
15153  if(!CheckForDuplicateCrossReferences(1, TDEntry.HeadCode, AVEntry.NonRepeatingShuttleLinkHeadCode, GiveMessages))
15154  {
15155  Utilities->CallLogPop(1585);
15156  return(false); // error message given in called function
15157  }
15158  }
15159  }
15160  }
15161 
15162  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15163  {
15164  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15165  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15166  {
15167  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15168  if((AVEntry.Command != "Sns-sh") && (AVEntry.Command != "Snt-sh") && (AVEntry.Command != "Fns-sh") && (AVEntry.Command != "Frh-sh"))
15169  {
15170  if(AVEntry.OtherHeadCode != "")
15171  {
15172  if(!CheckCrossReferencesAndSetData(0, TDEntry.HeadCode, AVEntry.OtherHeadCode, false, true, GiveMessages))
15173  // false = non-shuttle
15174  {
15175  Utilities->CallLogPop(864);
15176  return(false); // error message given in called function
15177  }
15178  }
15179  }
15180  }
15181  }
15182 
15183  // now repeat the check just for the shuttles
15184  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15185  {
15186  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15187  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15188  {
15189  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15190  if((AVEntry.Command == "Sns-sh") || (AVEntry.Command == "Snt-sh") || (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "Frh-sh"))
15191  {
15192  if(AVEntry.OtherHeadCode != "")
15193  {
15194  if(!CheckCrossReferencesAndSetData(1, TDEntry.HeadCode, AVEntry.OtherHeadCode, true, true, GiveMessages))
15195  // true = shuttle
15196  {
15197  Utilities->CallLogPop(1100);
15198  return(false); // error message given in called function
15199  }
15200  }
15201  }
15202  }
15203  }
15204 
15205  // check for proper non-repeating link cross references and that they have no repeats & that times are consistent
15206  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15207  {
15208  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15209  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15210  {
15211  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15212  if(AVEntry.NonRepeatingShuttleLinkHeadCode != "")
15213  {
15214  if(!CheckNonRepeatingShuttleLinksAndSetData(0, TDEntry.HeadCode, AVEntry.NonRepeatingShuttleLinkHeadCode, true, GiveMessages))
15215  {
15216  Utilities->CallLogPop(1060);
15217  return(false); // error message given in called function
15218  }
15219  }
15220  }
15221  }
15222 
15223  // check that each shuttle start ends either in Fns or Fxx-sh (though a single service can't end in Fxx-sh), and that
15224  // when the Fxx-sh is reached it references the original start and not another shuttle - not allowed to link two shuttles,
15225  // don't ever need to and as designed would skip repeats
15226  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15227  {
15228  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15229  {
15230  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15231  if((AVEntry.Command == "Sns-sh") || (AVEntry.Command == "Snt-sh"))
15232  {
15233  if(!CheckShuttleServiceIntegrity(0, &(TrainDataVector.at(x)), GiveMessages))
15234  {
15235  Utilities->CallLogPop(1090);
15236  return(false); // error message given in called function
15237  }
15238  }
15239  }
15240  }
15241 
15242  // check all entries have all types set to something
15243  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15244  {
15245  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15246  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15247  {
15248  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15249  if(AVEntry.FormatType == NoFormat)
15250  {
15251  throw Exception("Error - timetable ActionVector event no. " + AnsiString(y) + " has FormatType unset for: " + TDEntry.HeadCode);
15252  }
15253  else if(AVEntry.SequenceType == NoSequence)
15254  {
15255  throw Exception("Error - timetable ActionVector event no. " + AnsiString(y) + " has SequenceType unset for: " + TDEntry.HeadCode);
15256  }
15257  else if(AVEntry.LocationType == NoLocation)
15258  {
15259  throw Exception("Error - timetable ActionVector event no. " + AnsiString(y) + " has LocationType unset for: " + TDEntry.HeadCode);
15260  }
15261  else if(AVEntry.ShuttleLinkType == NoShuttleLink)
15262  {
15263  throw Exception("Error - timetable ActionVector event no. " + AnsiString(y) + " has ShuttleLinkType unset for: " + TDEntry.HeadCode);
15264  }
15265  }
15266  }
15267 
15268  // all OK if reach here, so set up the TrainOperatingDataVector (already has one entry) & NumberOfTrains
15269  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15270  {
15271  TTrainDataEntry & TDEntry = TrainDataVector.at(x);
15272  // non-const reference so can alter content
15273  TTrainOperatingData TData;
15274  const TActionVectorEntry &LastAVEntry = TDEntry.ActionVector.at(TDEntry.ActionVector.size() - 1);
15275  if(LastAVEntry.FormatType == Repeat) // check if a repeat
15276  {
15277 /*
15278  class TTrainOperatingData
15279  {
15280  public:
15281  int TrainID; - default, set at construction
15282  TActionEventType EventReported; used during operation
15283  TRunningEntry RunningEntry; - default, set at construction
15284  TTrainOperatingData() {TrainID = -1; EventReported= NoEvent; RunningEntry=NotStarted;} //constructor, values set to defaults
15285  };
15286 */
15287  TDEntry.NumberOfTrains = LastAVEntry.NumberOfRepeats + 1;
15288  for(int y = 1; y < TDEntry.NumberOfTrains; y++)
15289  {
15290  TDEntry.TrainOperatingDataVector.push_back(TData);
15291  }
15292  }
15293  else
15294  {
15295  TDEntry.NumberOfTrains = 1;
15296  }
15297  }
15298 
15299  // check that don't include any Continuation names
15300  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15301  {
15302  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15303  {
15304  AnsiString LocName = TrainDataVector.at(x).ActionVector.at(y).LocationName;
15305  AnsiString HC = TrainDataVector.at(x).HeadCode;
15306  if(LocName != "")
15307  {
15308  if(Track->ContinuationNameMap.find(LocName) != Track->ContinuationNameMap.end())
15309  {
15310  SecondPassMessage(GiveMessages, "Error in timetable - continuation names (" + LocName + ") must not be included, see service " + HC);
15311  TrainDataVector.clear();
15312  Utilities->CallLogPop(1578);
15313  return(false);
15314  }
15315  }
15316  }
15317  }
15318 
15319  // check that all repeat times below 96h
15320  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15321  {
15322  int NumRepeats = (TrainDataVector.at(x).NumberOfTrains) - 1;
15323  int IncMinutes = 0;
15324  if(TrainDataVector.at(x).ActionVector.at(TrainDataVector.at(x).ActionVector.size() - 1).FormatType == Repeat)
15325  {
15326  IncMinutes = TrainDataVector.at(x).ActionVector.at(TrainDataVector.at(x).ActionVector.size() - 1).RearStartOrRepeatMins;
15327  }
15328  else
15329  {
15330  continue; // basic times already checked in CheckTimeValidity
15331  }
15332  AnsiString HC = TrainDataVector.at(x).HeadCode;
15333  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15334  {
15335  if((double)TrainDataVector.at(x).ActionVector.at(y).EventTime > -1)
15336  {
15337  if(((double)GetRepeatTime(32, TrainDataVector.at(x).ActionVector.at(y).EventTime, NumRepeats, IncMinutes) >= 3.9994))
15338  {
15339  SecondPassMessage(GiveMessages, "Error in timetable - a repeat time exceeds 95h 59m, see service " + HC); // 3d 23h 59m = 3.9993055556
15340  TrainDataVector.clear();
15341  Utilities->CallLogPop(1818);
15342  return(false);
15343  }
15344  }
15345  if((double)TrainDataVector.at(x).ActionVector.at(y).ArrivalTime > -1)
15346  {
15347  if(((double)GetRepeatTime(33, TrainDataVector.at(x).ActionVector.at(y).EventTime, NumRepeats, IncMinutes) >= 3.9994))
15348  // 3d 23h 59m = 3.9993055556
15349  {
15350  SecondPassMessage(GiveMessages, "Error in timetable - a repeat event time exceeds 95h 59m, see service " + HC);
15351  TrainDataVector.clear();
15352  Utilities->CallLogPop(1819);
15353  return(false);
15354  }
15355  }
15356  if((double)TrainDataVector.at(x).ActionVector.at(y).DepartureTime > -1)
15357  {
15358  if(((double)GetRepeatTime(34, TrainDataVector.at(x).ActionVector.at(y).EventTime, NumRepeats, IncMinutes) >= 3.9994))
15359  // 3d 23h 59m = 3.9993055556
15360  {
15361  SecondPassMessage(GiveMessages, "Error in timetable - a repeat event time exceeds 95h 59m, see service " + HC);
15362  TrainDataVector.clear();
15363  Utilities->CallLogPop(1820);
15364  return(false);
15365  }
15366  }
15367  }
15368  }
15369 
15370  // Now that all set up change any extended headcodes back to ordinary headcodes
15371  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15372  {
15373  StripExcessFromHeadCode(0, TrainDataVector.at(x).HeadCode);
15374  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15375  {
15376  StripExcessFromHeadCode(1, TrainDataVector.at(x).ActionVector.at(y).OtherHeadCode);
15377  StripExcessFromHeadCode(2, TrainDataVector.at(x).ActionVector.at(y).NonRepeatingShuttleLinkHeadCode);
15378  }
15379  }
15380 
15381  // SaveTrainDataVectorToFile(0);//for testing purposes
15383  Utilities->CallLogPop(782);
15384  return(true);
15385 }
15386 
15387 // ---------------------------------------------------------------------------
15388 // Moving successors: TimeLoc arr/TimeTimeLoc/pas/Fer; Only call this when moving so if TimeLoc found it must be a departure
15390 {
15391  return ((AVEntry.FormatType == TimeLoc) || (AVEntry.FormatType == TimeTimeLoc) || (AVEntry.Command == "pas") || (AVEntry.Command == "Fer"));
15392 }
15393 
15394 // ---------------------------------------------------------------------------
15395 // AtLoc successors: TimeLoc dep/jbo/fsp/rsp/cdt/dsc/cms/Frh/Fns/Fjo/Frh-sh/Fns-sh/F-nshs; Only call this when stationary so if TimeLoc found it must be an arrival
15397 {
15398  return ((AVEntry.FormatType == TimeLoc) || (AVEntry.Command == "jbo") || (AVEntry.Command == "fsp") || (AVEntry.Command == "rsp") ||
15399  (AVEntry.Command == "cdt") || (AVEntry.Command == "dsc") || (AVEntry.Command == "Frh") || (AVEntry.Command == "Fns") || (AVEntry.Command == "Fjo") || (AVEntry.Command == "Frh-sh") ||
15400  (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "F-nshs") || (AVEntry.Command == "cms"));
15401 }
15402 
15403 // ---------------------------------------------------------------------------
15404 
15405 void TTrainController::StripExcessFromHeadCode(int Caller, AnsiString &HeadCode)
15406 {
15407  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",StripExcessFromHeadCode," + HeadCode);
15408  if(HeadCode.Length() > 4) // ignore otherwise
15409  {
15410  HeadCode = HeadCode.SubString(HeadCode.Length() - 3, 4);
15411  }
15412  Utilities->CallLogPop(1593);
15413 }
15414 
15415 // ---------------------------------------------------------------------------
15416 
15417 bool TTrainController::CheckForDuplicateCrossReferences(int Caller, AnsiString MainHeadCode, AnsiString SecondHeadCode, bool GiveMessages)
15418 {
15419  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckForDuplicateCrossReferences," + MainHeadCode + "," +
15420  SecondHeadCode);
15421  int ForwardCount = 0;
15422  int ReverseCount = 0;
15423 
15424  if(MainHeadCode == SecondHeadCode)
15425  {
15426  SecondPassMessage(GiveMessages, "Error in timetable - Service " + MainHeadCode + " has an event that references itself");
15427  TrainDataVector.clear();
15428  Utilities->CallLogPop(1594);
15429  return(false);
15430  }
15431  // forward check
15432  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15433  {
15434  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15435  if(TDEntry.HeadCode == MainHeadCode)
15436  {
15437  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15438  {
15439  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15440  if(AVEntry.OtherHeadCode == SecondHeadCode)
15441  {
15442  ForwardCount++;
15443  }
15444  if(AVEntry.NonRepeatingShuttleLinkHeadCode == SecondHeadCode)
15445  // need own check in case both 'Other' & 'NonRepeating' have same headcode
15446  {
15447  ForwardCount++;
15448  }
15449  }
15450  }
15451  }
15452  if(ForwardCount == 0)
15453  // this is an exception because the headcodes are selected in the same order as the forward check
15454  {
15455  throw Exception("Error, ForwardCount == 0 in CheckForDuplicateCrossReferences after called with found values");
15456  }
15457  if(ForwardCount > 2)
15458  // can have 2 if one is Sns-sh linking from another leg of the shuttle, and Fns links out to that same leg
15459  {
15460  SecondPassMessage(GiveMessages, "Error in timetable - found more than two references to " + SecondHeadCode + " from a train whose headcode is " +
15461  MainHeadCode + ". Check the service cross references from each service, and check whether one or other service is listed twice or more.");
15462  TrainDataVector.clear();
15463  Utilities->CallLogPop(1587);
15464  return(false);
15465  }
15466  // reverse check
15467  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15468  {
15469  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15470  if(TDEntry.HeadCode == SecondHeadCode)
15471  {
15472  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15473  {
15474  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15475  if(AVEntry.OtherHeadCode == MainHeadCode)
15476  {
15477  ReverseCount++;
15478  }
15479  if(AVEntry.NonRepeatingShuttleLinkHeadCode == MainHeadCode)
15480  {
15481  ReverseCount++;
15482  }
15483  }
15484  }
15485  }
15486 
15487  if(ReverseCount == 0)
15488  {
15489  SecondPassMessage(GiveMessages, "Error in timetable - cross reference missing in either " + MainHeadCode + " or " + SecondHeadCode);
15490  TrainDataVector.clear();
15491  Utilities->CallLogPop(1588);
15492  return(false);
15493  }
15494  if(ReverseCount > 2)
15495  // can have 2 if one is a second shuttle leg with a link in from Fns, and it links out to the same service with Fxx-sh
15496  {
15497  SecondPassMessage(GiveMessages, "Error in timetable - found more than two references to " + MainHeadCode + " from a train whose headcode is " +
15498  SecondHeadCode + ". Check the service cross references from each service, and check whether one or other service is listed twice or more.");
15499  TrainDataVector.clear();
15500  Utilities->CallLogPop(1589);
15501  return(false);
15502  }
15503  if(ForwardCount != ReverseCount)
15504  {
15505  SecondPassMessage(GiveMessages, "Error in timetable - " + MainHeadCode + " has a different number of references to " + SecondHeadCode +
15506  " than the other way round");
15507  TrainDataVector.clear();
15508  Utilities->CallLogPop(1610);
15509  return(false);
15510  }
15511  Utilities->CallLogPop(1590);
15512  return(true);
15513 }
15514 
15515 // ---------------------------------------------------------------------------
15516 
15517 bool TTrainController::CheckCrossReferencesAndSetData(int Caller, AnsiString MainHeadCode, AnsiString OtherHeadCode, bool Shuttle, bool SetDataAndCheckLocations, bool GiveMessages)
15518 /* Return false for no find or more than one find, check correct types of link
15519  First run through all trains whose headcode is the MainHeadCode (may be > 1) & for each entry whose
15520  'other' is OtherHeadCode increment a forward counter. Keep a pointer to the 'OtherHeadCode' entry for use later
15521  Must be exactly 1 forward count. NB Forward relates to MainHeadCode
15522  Then do the same in reverse.
15523  Using the pointers check the event times, then check that the locations & commands match - if main is a split then other must be Sfs;
15524  if main is Fns other must be Sns; if main is jbo other must be Fjo.
15525  Also check platform lengths OK for a split location (call to Track function for this - at least one platform at location has to be long
15526  enough). If all succeeds so far set the relevant OtherHeadCodeStartingEntryPtr to the new service starting point + train information
15527  for Sfs & Sns services. Finally check the repeat entries if present are consistent
15528 
15529  Check all except the NonRepeatingShuttleLinkHeadCodes, which only occur from F-nshs to Sns-sh, and from Fns-sh to
15530  Sns-fsh. All others should check out OK, but check shuttles & non-shuttles separately.
15531 
15532  /NB prohibit main & other headcodes being same, causes probs in failing to recognise locations
15533 */
15534 
15535 {
15536  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckCrossReferencesAndSetData," + MainHeadCode + "," + OtherHeadCode);
15537  int ForwardCount = 0;
15538  int ReverseCount = 0;
15539  unsigned int ForwardTDVectorNumber, ReverseTDVectorNumber;
15540  TActionVectorEntry *ReverseEntryPtr = 0, *ForwardEntryPtr = 0;
15541  TTrainDataEntry *MainTrainDataPtr = 0;
15542  TTrainDataEntry *OtherTrainDataPtr = 0;
15543 
15544  // forward check
15545  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15546  {
15547  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15548  if(TDEntry.HeadCode == MainHeadCode)
15549  {
15550  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15551  {
15552  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15553  if(!Shuttle && (AVEntry.Command != "Sns-sh") && (AVEntry.Command != "Snt-sh") && (AVEntry.Command != "Fns-sh") && (AVEntry.Command != "Frh-sh"))
15554  {
15555  if(AVEntry.OtherHeadCode == OtherHeadCode)
15556  {
15557  MainTrainDataPtr = &TrainDataVector.at(x);
15558  ForwardEntryPtr = &AVEntry;
15559  ForwardCount++;
15560  ForwardTDVectorNumber = x;
15561  }
15562  }
15563  else if(Shuttle && ((AVEntry.Command == "Sns-sh") || (AVEntry.Command == "Snt-sh") || (AVEntry.Command == "Fns-sh") ||
15564  (AVEntry.Command == "Frh-sh")))
15565  {
15566  if(AVEntry.OtherHeadCode == OtherHeadCode)
15567  {
15568  MainTrainDataPtr = &TrainDataVector.at(x);
15569  ForwardEntryPtr = &AVEntry;
15570  ForwardCount++;
15571  ForwardTDVectorNumber = x;
15572  }
15573  }
15574  }
15575  }
15576  }
15577  if(ForwardCount == 0)
15578  // this is an exception because the headcodes are selected in the same order as the forward check
15579  {
15580  throw Exception("Error, ForwardCount == 0 in CheckCrossReferencesAndSetData after called with found values");
15581  }
15582  if(ForwardCount > 1)
15583  {
15584  SecondPassMessage(GiveMessages, "Error in timetable - found more than one reference to " + OtherHeadCode + " from a train whose headcode is " +
15585  MainHeadCode);
15586  TrainDataVector.clear();
15587  Utilities->CallLogPop(836);
15588  return(false);
15589  }
15590  // reverse check
15591  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15592  {
15593  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15594  if(TDEntry.HeadCode == OtherHeadCode)
15595  {
15596  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15597  {
15598  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15599  if(!Shuttle && (AVEntry.Command != "Sns-sh") && (AVEntry.Command != "Snt-sh") && (AVEntry.Command != "Fns-sh") && (AVEntry.Command != "Frh-sh"))
15600  {
15601  if(AVEntry.OtherHeadCode == MainHeadCode)
15602  {
15603  OtherTrainDataPtr = &TrainDataVector.at(x);
15604  ReverseCount++;
15605  ReverseEntryPtr = &AVEntry;
15606  ReverseTDVectorNumber = x;
15607  }
15608  }
15609  else if(Shuttle && ((AVEntry.Command == "Sns-sh") || (AVEntry.Command == "Snt-sh") || (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "Frh-sh")))
15610  {
15611  if(AVEntry.OtherHeadCode == MainHeadCode)
15612  {
15613  OtherTrainDataPtr = &TrainDataVector.at(x);
15614  ReverseCount++;
15615  ReverseEntryPtr = &AVEntry;
15616  ReverseTDVectorNumber = x;
15617  }
15618  }
15619  }
15620  }
15621  }
15622 
15623  if(ReverseCount == 0)
15624  {
15625  SecondPassMessage(GiveMessages, "Error in timetable - cross reference missing in either " + MainHeadCode + " or " + OtherHeadCode);
15626  TrainDataVector.clear();
15627  Utilities->CallLogPop(837);
15628  return(false);
15629  }
15630  if(ReverseCount > 1)
15631  {
15632  SecondPassMessage(GiveMessages, "Error in timetable - found more than one reference to " + MainHeadCode + " from a train whose headcode is " +
15633  OtherHeadCode);
15634  TrainDataVector.clear();
15635  Utilities->CallLogPop(838);
15636  return(false);
15637  }
15638  // these will all be false for !Shuttle
15639  bool ForwardShuttleStart = ((ForwardEntryPtr->Command == "Sns-sh") || (ForwardEntryPtr->Command == "Snt-sh"));
15640  bool ForwardShuttleFinish = ((ForwardEntryPtr->Command == "Fns-sh") || (ForwardEntryPtr->Command == "Frh-sh"));
15641  bool ReverseShuttleStart = ((ReverseEntryPtr->Command == "Sns-sh") || (ReverseEntryPtr->Command == "Snt-sh"));
15642  bool ReverseShuttleFinish = ((ReverseEntryPtr->Command == "Fns-sh") || (ReverseEntryPtr->Command == "Frh-sh"));
15643 
15644  if(Shuttle && MainTrainDataPtr->ActionVector.back().FormatType != Repeat)
15645  {
15646  SecondPassMessage(GiveMessages, "Error in timetable - shuttle train " + MainHeadCode + " does not have a repeat");
15647  TrainDataVector.clear();
15648  Utilities->CallLogPop(1058);
15649  return(false);
15650  }
15651  if(Shuttle && OtherTrainDataPtr->ActionVector.back().FormatType != Repeat)
15652  {
15653  SecondPassMessage(GiveMessages, "Error in timetable - shuttle train " + OtherHeadCode + " does not have a repeat");
15654  TrainDataVector.clear();
15655  Utilities->CallLogPop(1059);
15656  return(false);
15657  }
15658  if(SetDataAndCheckLocations)
15659  {
15660  if(ForwardEntryPtr->LocationName == "")
15661  {
15662  SecondPassMessage(GiveMessages, "Error in timetable - location error in cross referenced trains " + MainHeadCode + " and " + OtherHeadCode +
15663  ". One or other service does not have a location set");
15664  TrainDataVector.clear();
15665  Utilities->CallLogPop(526);
15666  return(false);
15667  }
15668  if(ReverseEntryPtr->LocationName == "")
15669  {
15670  SecondPassMessage(GiveMessages, "Error in timetable - location error in cross referenced trains " + MainHeadCode + " and " + OtherHeadCode +
15671  ". One or other service does not have a location set");
15672  TrainDataVector.clear();
15673  Utilities->CallLogPop(527);
15674  return(false);
15675  }
15676  if(ForwardEntryPtr->LocationName != ReverseEntryPtr->LocationName)
15677  {
15678  SecondPassMessage(GiveMessages, "Error in timetable - cross referenced train " + OtherHeadCode +
15679  " is at a different location to the referencing train " + MainHeadCode);
15680  TrainDataVector.clear();
15681  Utilities->CallLogPop(842);
15682  return(false);
15683  }
15684  }
15685  // ignore shuttle repeat links for first time check
15686  if(!Shuttle)
15687  {
15688  if(ForwardEntryPtr->EventTime != ReverseEntryPtr->EventTime)
15689  {
15690  SecondPassMessage(GiveMessages, "Error in timetable - cross referenced train " + OtherHeadCode +
15691  " has a different event time to the referencing train " + MainHeadCode);
15692  TrainDataVector.clear();
15693  Utilities->CallLogPop(525);
15694  return(false);
15695  }
15696  }
15697  // need to allow for repeat times multiplying up by repeating time for shuttle repeat links
15698  // no need to check from reverse to forward as already checked links consistent, and if include will send message twice
15699  if(ForwardShuttleStart && ReverseShuttleFinish)
15700  // Shuttle must be true if these are true
15701  {
15702  if(!CheckShuttleRepeatTime(0, ForwardEntryPtr->EventTime, ReverseEntryPtr->EventTime, OtherTrainDataPtr->ActionVector.back().RearStartOrRepeatMins))
15703  {
15704  SecondPassMessage(GiveMessages, "Error in timetable - shuttle service " + MainHeadCode +
15705  " first repeat restart time not consistent with finish service " + OtherHeadCode);
15706  TrainDataVector.clear();
15707  Utilities->CallLogPop(1055);
15708  return(false);
15709  }
15710  }
15711  if((ReverseEntryPtr->Command == "Sfs") || (ReverseEntryPtr->Command == "Sns"))
15712  // doesn't matter about ForwardEntryPtr being Sfs/Sns as called for every occurrence of an 'OtherHeadCode' so won't escape
15713  {
15714  if(ReverseTDVectorNumber == ForwardTDVectorNumber)
15715  {
15716  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sfs' or 'Sns' event (" + OtherHeadCode +
15717  ") appears in the same sequence as the corresponding linked event " + MainHeadCode);
15718  TrainDataVector.clear();
15719  Utilities->CallLogPop(528);
15720  return(false);
15721  }
15722  }
15723  if(ReverseEntryPtr->Command == "Fjo")
15724  // doesn't matter about ForwardEntryPtr being Fjo as called for every occurrence of an 'OtherHeadCode' so won't escape
15725  {
15726  if(ReverseTDVectorNumber == ForwardTDVectorNumber)
15727  {
15728  SecondPassMessage(GiveMessages, "Error in timetable - an 'Fjo' event (" + OtherHeadCode +
15729  ") appears in the same sequence as the corresponding linked event " + MainHeadCode);
15730  TrainDataVector.clear();
15731  Utilities->CallLogPop(862);
15732  return(false);
15733  }
15734  }
15735  if(ReverseEntryPtr->Command == "Fns")
15736  // doesn't matter about ForwardEntryPtr being Fns as called for every occurrence of an 'OtherHeadCode' so won't escape
15737  {
15738  if(ReverseTDVectorNumber == ForwardTDVectorNumber)
15739  {
15740  SecondPassMessage(GiveMessages, "Error in timetable - an 'Fns' event (" + OtherHeadCode +
15741  ") appears in the same sequence as the corresponding linked event " + MainHeadCode);
15742  TrainDataVector.clear();
15743  Utilities->CallLogPop(529);
15744  return(false);
15745  }
15746  }
15747  if(ForwardEntryPtr->Command == "Sfs")
15748  {
15749  if((ReverseEntryPtr->Command != "fsp") && (ReverseEntryPtr->Command != "rsp"))
15750  {
15751  SecondPassMessage(GiveMessages,
15752  "Error in timetable - unable to find a corresponding split train event for the train that starts from a split whose headcode is " +
15753  MainHeadCode);
15754  TrainDataVector.clear();
15755  Utilities->CallLogPop(530);
15756  return(false);
15757  }
15758  }
15759  if((ForwardEntryPtr->Command == "fsp") || (ForwardEntryPtr->Command == "rsp"))
15760  {
15761  if(ReverseEntryPtr->Command != "Sfs")
15762  {
15763  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'Sfs' event for the train split whose headcode is " +
15764  MainHeadCode);
15765  TrainDataVector.clear();
15766  Utilities->CallLogPop(839);
15767  return(false);
15768  }
15769  else
15770  {
15771  if(SetDataAndCheckLocations)
15772  {
15773  if(!(Track->TimetabledLocationNameAllocated(4, ForwardEntryPtr->LocationName)))
15774  {
15775  SecondPassMessage(GiveMessages, "Error in timetable - can't find timetabled location '" + ForwardEntryPtr->LocationName + "' in railway - perhaps there are concourses without platforms?");
15776  TrainDataVector.clear();
15777  Utilities->CallLogPop(849);
15778  return(false);
15779  }
15780  if(!(Track->OneNamedLocationElementAtLocation(0, ForwardEntryPtr->LocationName)))
15781  {
15782  SecondPassMessage(GiveMessages, "Error in timetable - can't find any named location elements at '" + ForwardEntryPtr->LocationName + "' - perhaps there are concourses without platforms?");
15783  TrainDataVector.clear();
15784  Utilities->CallLogPop(850);
15785  return(false);
15786  }
15787 //determine whether LocationName is a station or non-station
15788  bool StationLocation = false;
15789  for(TTrack::TTrackVectorIterator TEIt = Track->InactiveTrackVector.begin(); TEIt != Track->InactiveTrackVector.end(); TEIt++)
15790  {
15791  if(TEIt->LocationName == ForwardEntryPtr->LocationName)
15792  {
15793  if(TEIt->TrackType != NamedNonStationLocation)
15794  {
15795  StationLocation = true;
15796  }
15797  }
15798  }
15799  if(StationLocation)
15800  {
15801  if(!(Track->OneStationLongEnoughForSplit(0, ForwardEntryPtr->LocationName)))
15802  {
15803  SecondPassMessage(GiveMessages, "Error in timetable - location too short to split a train at " + ForwardEntryPtr->LocationName);
15804  TrainDataVector.clear();
15805  Utilities->CallLogPop(846);
15806  return(false);
15807  }
15808  }
15809  else
15810  {
15811  if(!(Track->OneNonStationLongEnoughForSplit(0, ForwardEntryPtr->LocationName)))
15812  {
15813  SecondPassMessage(GiveMessages, "Error in timetable - location too short to split a train at " + ForwardEntryPtr->LocationName);
15814  TrainDataVector.clear();
15815  Utilities->CallLogPop(2660);
15816  return(false);
15817  }
15818  }
15819  ForwardEntryPtr->LinkedTrainEntryPtr = OtherTrainDataPtr;
15820  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
15821  if(OtherTrainDataPtr->FixedDescription == "") //name changed at v2.16.1
15822  {
15823  OtherTrainDataPtr->FixedDescription = MainTrainDataPtr->FixedDescription;
15824  }
15825  }
15826  // NB: May not be set if main train is a service continuation without a description, if so can't do much about it but doesn't affect operation, just the train information display
15827  OtherTrainDataPtr->MaxRunningSpeed = MainTrainDataPtr->MaxRunningSpeed;
15828  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
15829  }
15830  }
15831  if(ForwardEntryPtr->Command == "Sns")
15832  {
15833  if(ReverseEntryPtr->Command != "Fns")
15834  {
15835  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'Fns' event for the 'Sns' train whose headcode is " +
15836  MainHeadCode + " and is formed from a service with headcode " + OtherHeadCode);
15837  TrainDataVector.clear();
15838  Utilities->CallLogPop(531);
15839  return(false);
15840  }
15841  }
15842  if(ForwardEntryPtr->Command == "Fns")
15843  {
15844  if(ReverseEntryPtr->Command != "Sns")
15845  {
15846  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'Sns' event for the train whose headcode is " + MainHeadCode +
15847  " and forms a new service with headcode " + OtherHeadCode);
15848  TrainDataVector.clear();
15849  Utilities->CallLogPop(840);
15850  return(false);
15851  }
15852  else
15853  {
15854  if(SetDataAndCheckLocations)
15855  {
15856  ForwardEntryPtr->LinkedTrainEntryPtr = OtherTrainDataPtr;
15857  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
15858  if(OtherTrainDataPtr->FixedDescription == "") //name changed at v2.16.1
15859  {
15860  OtherTrainDataPtr->FixedDescription = MainTrainDataPtr->FixedDescription;
15861  }
15862  }
15863  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
15864  OtherTrainDataPtr->MaxRunningSpeed = MainTrainDataPtr->MaxRunningSpeed;
15865  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
15866  }
15867  }
15868  if(ForwardEntryPtr->Command == "jbo")
15869  {
15870  if(ReverseEntryPtr->Command != "Fjo")
15871  {
15872  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'Fjo' event for the train whose headcode is " + MainHeadCode +
15873  " and is joined by a train with headcode " + OtherHeadCode);
15874  TrainDataVector.clear();
15875  Utilities->CallLogPop(841);
15876  return(false);
15877  }
15878  }
15879  if(ForwardEntryPtr->Command == "Fjo")
15880  {
15881  if(ReverseEntryPtr->Command != "jbo")
15882  {
15883  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'jbo' event for the train whose headcode is " + MainHeadCode +
15884  " and joins a train with headcode " + OtherHeadCode);
15885  TrainDataVector.clear();
15886  Utilities->CallLogPop(532);
15887  return(false);
15888  }
15889  else
15890  {
15891  if(SetDataAndCheckLocations)
15892  {
15893  ForwardEntryPtr->LinkedTrainEntryPtr = OtherTrainDataPtr;
15894  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
15895  }
15896 /*
15897  if((MainTrainDataPtr->MaxRunningSpeed > 5) && (MainTrainDataPtr->MaxRunningSpeed < OtherTrainDataPtr->MaxRunningSpeed))
15898  {
15899  OtherTrainDataPtr->MaxRunningSpeed = MainTrainDataPtr->MaxRunningSpeed; //this should only be set when the trains join
15900  } not when internal timetable being compiled. Dropped at v2.15.0 when Brent Mackie reported error on 18/02/23 via discord
15901 
15902  //added test for > 5 [5 used instead of 0 because of possible floating point errors - though unlikely] above at v1.3.1 because the train will have a
15903  //zero MaxRunningSpeed if it continues from another service - its max speed is set when it takes over from the other service
15904  //notified of this problem by Ian Walker in his email of 25/03/13. Probably redundant anyway because the max speed is reduced at the changeover if the
15905  //'joined by' train's max speed is less.
15906 */
15907  }
15908  }
15909  if(ForwardShuttleStart)
15910  // (ForwardEntryPtr->Command == "Sns-sh") || (ForwardEntryPtr->Command == "Snt-sh"))
15911  {
15912  if(!ReverseShuttleFinish)
15913  // (ReverseEntryPtr->Command != "Fns-sh") && (ReverseEntryPtr->Command != "Frh-sh"))
15914  {
15915  SecondPassMessage(GiveMessages, "Error in timetable - incorrect shuttle link to train whose headcode is " + MainHeadCode +
15916  " from train whose headcode is " + OtherHeadCode + ", has to be Fns-sh, Frh-sh");
15917  TrainDataVector.clear();
15918  Utilities->CallLogPop(1056);
15919  return(false);
15920  }
15921  }
15922  if(ReverseShuttleStart)
15923  // (ReverseEntryPtr->Command == "Sns-sh") || (ReverseEntryPtr->Command == "Snt-sh"))
15924  {
15925  if(!ForwardShuttleFinish)
15926  // (ForwardEntryPtr->Command != "Fns-sh") && (ForwardEntryPtr->Command != "Frh-sh"))
15927  {
15928  SecondPassMessage(GiveMessages, "Error in timetable - incorrect shuttle link to train whose headcode is " + OtherHeadCode +
15929  " from train whose headcode is " + MainHeadCode + ", has to be Fns-sh, Frh-sh");
15930  TrainDataVector.clear();
15931  Utilities->CallLogPop(1057);
15932  return(false);
15933  }
15934  else
15935  {
15936  if(SetDataAndCheckLocations)
15937  {
15938  ForwardEntryPtr->LinkedTrainEntryPtr = OtherTrainDataPtr;
15939  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
15940  }
15941 /* don't need LinkedTrainEntryPtr for 'OtherTrain' & don't need data transfer as this is done in the
15942  non-repeating link for Sns-sh & is provided at the outset for Snt-sh
15943 */
15944  }
15945  }
15946  // check repeat information consistent if present
15947  // note that won't be affected by the non-repeating shuttle links as these are in NonRepeatingShuttleLinkHeadCode
15948  // and those not accessed here
15949 
15950  // still need to check the non-repeating links and that they have no repeats - do that outside this function
15951  bool MainRepeat = false, OtherRepeat = false;
15952  TActionVectorEntry MainRepeatEntry, OtherRepeatEntry;
15953 
15954  if(MainTrainDataPtr->ActionVector.at(MainTrainDataPtr->ActionVector.size() - 1).FormatType == Repeat)
15955  {
15956  MainRepeat = true;
15957  MainRepeatEntry = MainTrainDataPtr->ActionVector.at(MainTrainDataPtr->ActionVector.size() - 1);
15958  }
15959  if(OtherTrainDataPtr->ActionVector.at(OtherTrainDataPtr->ActionVector.size() - 1).FormatType == Repeat)
15960  {
15961  OtherRepeat = true;
15962  OtherRepeatEntry = OtherTrainDataPtr->ActionVector.at(OtherTrainDataPtr->ActionVector.size() - 1);
15963  }
15964  if((MainRepeat && !OtherRepeat) || (!MainRepeat && OtherRepeat))
15965  {
15966  SecondPassMessage(GiveMessages, "Error in timetable - only one repeat is provided for the train whose headcode is " + MainHeadCode +
15967  " and the associated train with headcode " + OtherHeadCode);
15968  TrainDataVector.clear();
15969  Utilities->CallLogPop(844);
15970  return(false);
15971  }
15972  if(MainRepeat && OtherRepeat)
15973  {
15974  if((MainRepeatEntry.EventTime != OtherRepeatEntry.EventTime) || (MainRepeatEntry.RearStartOrRepeatMins != OtherRepeatEntry.RearStartOrRepeatMins) ||
15975  (MainRepeatEntry.FrontStartOrRepeatDigits != OtherRepeatEntry.FrontStartOrRepeatDigits) ||
15976  (MainRepeatEntry.NumberOfRepeats != OtherRepeatEntry.NumberOfRepeats))
15977  {
15978  SecondPassMessage(GiveMessages, "Error in timetable - repeat items don't correspond for the train whose headcode is " + MainHeadCode +
15979  " and the associated train with headcode " + OtherHeadCode);
15980  TrainDataVector.clear();
15981  Utilities->CallLogPop(845);
15982  return(false);
15983  }
15984  }
15985  Utilities->CallLogPop(863);
15986  return(true);
15987 }
15988 
15989 // ---------------------------------------------------------------------------
15990 
15991 void TTrainController::StripSpaces(int Caller, AnsiString &Input)
15992 // strip both leading and trailing spaces at ends of text and spaces before and after all commas and semicolons within the text
15993 {
15994  // strip spaces from extreme ends of input
15995  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",StripSpaces," + AnsiString(Input));
15996  if(Input == "")
15997  {
15998  Utilities->CallLogPop(856);
15999  return;
16000  }
16001  while(Input[1] == ' ')
16002  {
16003  if(Input.Length() > 1)
16004  {
16005  Input = Input.SubString(2, Input.Length() - 1);
16006  }
16007  else
16008  {
16009  Input = "";
16010  Utilities->CallLogPop(857);
16011  return;
16012  }
16013  }
16014  if(Input == "")
16015  {
16016  Utilities->CallLogPop(858);
16017  return;
16018  }
16019  while(Input[Input.Length()] == ' ')
16020  {
16021  if(Input.Length() > 1)
16022  {
16023  Input = Input.SubString(1, Input.Length() - 1);
16024  }
16025  else
16026  {
16027  Input = "";
16028  Utilities->CallLogPop(859);
16029  return;
16030  }
16031  }
16032  // now strip spaces immediately after all commas and semicolons within the text
16033  AnsiString Output = "";
16034  bool DelimiterFound = false;
16035 
16036  for(int x = 1; x < Input.Length() + 1; x++)
16037  {
16038  if(DelimiterFound)
16039  {
16040  if(Input[x] == ' ')
16041  {
16042  continue;
16043  }
16044  }
16045  if((Input[x] != ',') && (Input[x] != ';'))
16046  {
16047  DelimiterFound = false;
16048  Output = Output + Input[x];
16049  }
16050  else
16051  {
16052  DelimiterFound = true;
16053  Output = Output + Input[x];
16054  }
16055  }
16056  if(Output == "")
16057  {
16058  Input = "";
16059  Utilities->CallLogPop(860);
16060  return;
16061  }
16062  // now strip spaces immediately before all commas and semicolons within the text
16063  Input = Output;
16064  Output = "";
16065  DelimiterFound = false;
16066  for(int x = Input.Length(); x > 0; x--)
16067  {
16068  if(DelimiterFound)
16069  {
16070  if(Input[x] == ' ')
16071  {
16072  continue;
16073  }
16074  }
16075  if((Input[x] != ',') && (Input[x] != ';'))
16076  {
16077  DelimiterFound = false;
16078  Output = AnsiString(Input[x]) + Output;
16079  }
16080  else
16081  {
16082  DelimiterFound = true;
16083  Output = AnsiString(Input[x]) + Output;
16084  }
16085  }
16086  Input = Output;
16087  Utilities->CallLogPop(861);
16088 }
16089 
16090 // ---------------------------------------------------------------------------
16091 
16092 bool TTrainController::IsSNTEntryLocated(int Caller, const TTrainDataEntry &TDEntry, AnsiString &LocationName) //this is the original version re-instated at v2.15.0 Beta6
16093 // checks if an Snt or Snt-sh entry with zero starting speed is followed (somewhere, not necessarily immediately) by a TimeLoc & has the same LocationName
16094 // and if so returns true. Also returns true for Snt, not Snt-sh, if at least 1 start element is a location & the entry is either
16095 // a signaller control entry & speed is zero or it is followed immediately by Frh, Fjo, Fns or F-nshs (allows empty stock pickup).
16096 // Always return false for entry at a continuation (may be named but not a stop location). Note that no successor validity checks
16097 // are done in this function, they must be done elsewhere.
16098 //a starting speed > 0 always returns false
16099 {
16100  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsSNTEntryLocated," + AnsiString(TDEntry.HeadCode));
16101  const TActionVectorEntry &AVEntry0 = TDEntry.ActionVector.at(0);
16102  LocationName = "";
16103  if(TDEntry.StartSpeed > 0)
16104  {
16105  Utilities->CallLogPop(1784);
16106  return(false);
16107  }
16108  if((AVEntry0.Command != "Snt") && (AVEntry0.Command != "Snt-sh"))
16109  {
16110  throw Exception("Error, first event not 'Snt' or 'Snt-sh' in IsSNTEntryLocated");
16111  }
16113  {
16114  Utilities->CallLogPop(852);
16115  return(false);
16116  }
16117  AnsiString LocRear = Track->TrackElementAt(507, AVEntry0.RearStartOrRepeatMins).ActiveTrackElementName;
16118  AnsiString LocFront = Track->TrackElementAt(508, AVEntry0.FrontStartOrRepeatDigits).ActiveTrackElementName;
16119 
16120  if(LocRear != "")
16121  {
16122  LocationName = LocRear;
16123  }
16124  else
16125  {
16126  LocationName = LocFront;
16127  }
16128  if(LocationName == "")
16129  {
16130  Utilities->CallLogPop(1036);
16131  return(false);
16132  }
16133  if(AVEntry0.SignallerControl)
16134  {
16135  Utilities->CallLogPop(1773);
16136  return(true);
16137  }
16138 // here if not a signaller start entry so must be at least one more entry, and it must be at the same location as the Snt to be located
16139 
16140 //Ok Not ok continue
16141 
16142 //Frh if Snt Frh-sh cdt
16143 //Fns if Snt Fns-sh fsp or rsp
16144 //Fjo if Snt TimeTimeLoc jbo
16145 //F-nshs if Snt pas dsc or cms
16146 //TimeLoc dep Fer
16147 // TimeLoc arr
16148 
16149  for(unsigned int y = 0; y < TDEntry.ActionVector.size(); y++)
16150  {
16151  const TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(y);
16152  if(((AVEntry.Command == "Frh") || (AVEntry.Command == "Fjo") || (AVEntry.Command == "F-nshs") || (AVEntry.Command == "Fns")) && (AVEntry0.Command == "Snt")) // added Fjo at v2.0.0 for empty stock
16153  {
16154  Utilities->CallLogPop(1037);
16155  return(true);
16156  }
16157  if((AVEntry.FormatType == TimeLoc) && (AVEntry.LocationName == LocationName)) //will be a departure if same name- times not set yet so can't use them to confirm
16158  {
16159  Utilities->CallLogPop(2442);
16160  return(true);
16161  }
16162  if((AVEntry.FormatType == TimeLoc) && (AVEntry.LocationName != LocationName)) //arrival, not located
16163  {
16164  Utilities->CallLogPop(2438);
16165  return(false);
16166  }
16167  if((AVEntry.Command == "Fer") || (AVEntry.Command == "pas") || (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "Frh-sh") || (AVEntry.FormatType == TimeTimeLoc))
16168  {
16169  Utilities->CallLogPop(854);
16170  return(false);
16171  }
16172  if((AVEntry.Command == "cdt") || (AVEntry.Command == "dsc") || (AVEntry.Command == "cms") || (AVEntry.Command == "fsp") || (AVEntry.Command == "rsp") || (AVEntry.Command == "jbo"))
16173  {
16174  continue;
16175  }
16176  }
16177  Utilities->CallLogPop(855);
16178  return(false);
16179 
16180 }
16181 
16182 // ---------------------------------------------------------------------------
16183 
16184 bool TTrainController::CheckStartPositionValidity(int Caller, AnsiString RearElementStr, AnsiString FrontElementStr, bool GiveMessages)
16185 {
16186  // checks that the new train start elements are valid - both exist & are connected, and that not
16187  // attempting to start on a diverging leg (i.e. one segment on points & other on element connected to diverging leg) <--dropped at v2.18.0
16188  // & not starting with front on a continuation
16189  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckStartPositionValidity," + RearElementStr + "," + FrontElementStr);
16190  int RearPosition = 0, FrontPosition = 0; // RearExitPos = 0; //dropped at v2.18.0
16191 
16192  RearPosition = Track->GetTrackVectorPositionFromString(5, RearElementStr, GiveMessages);
16193  if(RearPosition < 0)
16194  // error message given in GetTrackVectorPositionFromString
16195  {
16196  Utilities->CallLogPop(759);
16197  return(false);
16198  }
16199  FrontPosition = Track->GetTrackVectorPositionFromString(6, FrontElementStr, GiveMessages);
16200  if(FrontPosition < 0)
16201  // error message given in GetTrackVectorPositionFromString
16202  {
16203  Utilities->CallLogPop(760);
16204  return(false);
16205  }
16206  TTrackElement RearTrackElement = Track->TrackElementAt(490, RearPosition);
16207  TTrackElement FrontTrackElement = Track->TrackElementAt(491, FrontPosition);
16208 // TTrackType RearType = RearTrackElement.TrackType; //dropped at v2.18.0
16209  TTrackType FrontType = FrontTrackElement.TrackType;
16210 
16211  // check front & rear connected
16212  for(int x = 0; x < 4; x++)
16213  {
16214  if(RearTrackElement.Conn[x] == FrontPosition)
16215  {
16216 // RearExitPos = x; //dropped at v2.18.0
16217  break;
16218  }
16219  if(x == 3) //if it gets here & not already found then not connected
16220  {
16221  TimetableMessage(GiveMessages, "Front element: " + FrontTrackElement.ElementID + " not linked to rear element: " + RearTrackElement.ElementID);
16222  Utilities->CallLogPop(762);
16223  return(false);
16224  }
16225  }
16226  // check not starting with front on a continuation
16227  if(FrontType == Continuation)
16228  {
16229  TimetableMessage(GiveMessages, "Front of train attempting to start on a continuation at: " + FrontElementStr);
16230  Utilities->CallLogPop(937);
16231  return(false);
16232  }
16233  // check not starting on a level crossing
16234  if(Track->IsLCAtHV(43, FrontTrackElement.HLoc, FrontTrackElement.VLoc))
16235  {
16236  TimetableMessage(GiveMessages, "Train attempting to start on a level crossing at: " + FrontElementStr);
16237  Utilities->CallLogPop(1951);
16238  return(false);
16239  }
16240  if(Track->IsLCAtHV(44, RearTrackElement.HLoc, RearTrackElement.VLoc))
16241  {
16242  TimetableMessage(GiveMessages, "Train attempting to start on a level crossing at: " + RearElementStr);
16243  Utilities->CallLogPop(1952);
16244  return(false);
16245  }
16246  // check if trying to start on diverging leg of points - allowed at v2.18.0, checks done during operation
16247 /*
16248  if((RearType == Points) && (RearExitPos == 3))
16249  {
16250  TimetableMessage(GiveMessages, "Front of train attempting to start on element connected to diverging points at: " + RearElementStr);
16251  Utilities->CallLogPop(936);
16252  return(false);
16253  }
16254 
16255  if((FrontType == Points) && (RearTrackElement.ConnLinkPos[RearExitPos] == 3))
16256  {
16257  TimetableMessage(GiveMessages, "Rear of train attempting to start on element connected to diverging points at: " + FrontElementStr);
16258  Utilities->CallLogPop(1808);
16259  return(false);
16260  }
16261 */
16262  Utilities->CallLogPop(905);
16263  return(true);
16264 }
16265 
16266 // ---------------------------------------------------------------------------
16267 
16268 bool TTrainController::CheckStartAllowable(int Caller, int RearPosition, int RearExitPos, AnsiString HeadCode, bool ReportFlag, TActionEventType &EventType)
16269 // Rear & front element validity already checked in CheckStartPositionValidity
16270 // This checks for points in correct orientation, no train at start position and not starting on a locked route
16271 {
16272  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckStartAllowable," + AnsiString(RearPosition) + "," +
16273  AnsiString(RearExitPos));
16274  TTrackElement RearTrackElement = Track->TrackElementAt(517, RearPosition);
16275 
16276  if(RearTrackElement.TrackType == Continuation)
16277  {
16278  EventType = FailTrainEntry;
16279  }
16280  else
16281  {
16282  EventType = FailCreateTrain;
16283  }
16284  int FrontPosition = RearTrackElement.Conn[RearExitPos];
16285  TTrackElement FrontTrackElement = Track->TrackElementAt(798, FrontPosition);
16286  int FrontEntryPos = RearTrackElement.ConnLinkPos[RearExitPos];
16287  TTrackType RearType = RearTrackElement.TrackType;
16288  TTrackType FrontType = FrontTrackElement.TrackType;
16289  AnsiString RearName, FrontName;
16290 
16291  if(RearTrackElement.ActiveTrackElementName != "")
16292  {
16293  RearName = RearTrackElement.ActiveTrackElementName;
16294  }
16295  else
16296  {
16297  RearName = RearTrackElement.ElementID;
16298  }
16299  if(FrontTrackElement.ActiveTrackElementName != "")
16300  {
16301  FrontName = FrontTrackElement.ActiveTrackElementName;
16302  }
16303  else
16304  {
16305  FrontName = FrontTrackElement.ElementID;
16306  }
16307  TPrefDirElement PrefDirElement; // needed for next function but not used
16308  int LockedVectorNumber; // needed for next function but not used
16309 
16310  if(AllRoutes->IsElementInLockedRouteGetPrefDirElementGetLockedVectorNumber(12, FrontPosition, FrontEntryPos, PrefDirElement, LockedVectorNumber))
16311  {
16312  if(ReportFlag)
16313  {
16314  if(EventType == FailCreateTrain)
16315  {
16316  EventType = FailCreateLockedRoute;
16317  }
16318  else
16319  {
16320  EventType = FailEnterLockedRoute;
16321  }
16322  LogActionError(47, HeadCode, "", EventType, FrontName);
16323  }
16324  Utilities->CallLogPop(940);
16325  return(false);
16326  }
16327  if(AllRoutes->IsElementInLockedRouteGetPrefDirElementGetLockedVectorNumber(13, RearPosition, RearExitPos, PrefDirElement, LockedVectorNumber))
16328  {
16329  if(ReportFlag)
16330  {
16331  if(EventType == FailCreateTrain)
16332  {
16333  EventType = FailCreateLockedRoute;
16334  }
16335  else
16336  {
16337  EventType = FailEnterLockedRoute;
16338  }
16339  LogActionError(48, HeadCode, "", EventType, RearName);
16340  }
16341  Utilities->CallLogPop(1809);
16342  return(false);
16343  }
16344  if((RearType != Bridge) && (RearTrackElement.TrainIDOnElement > -1))
16345  {
16346  if(ReportFlag)
16347  {
16348  LogActionError(27, HeadCode, "", EventType, RearName);
16349  }
16350  Utilities->CallLogPop(1810);
16351  return(false);
16352  }
16353  if((FrontType != Bridge) && (FrontTrackElement.TrainIDOnElement > -1))
16354  {
16355  if(ReportFlag)
16356  {
16357  if(EventType == FailCreateTrain)
16358  {
16359  LogActionError(28, HeadCode, "", EventType, FrontName);
16360  }
16361  else
16362  {
16363  LogActionError(43, HeadCode, "", EventType, RearName);
16364  }
16365  }
16366  Utilities->CallLogPop(941);
16367  return(false);
16368  }
16369  if(RearType == Bridge)
16370  {
16371  if((RearExitPos > 1) && (RearTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit23 > -1))
16372  {
16373  if(ReportFlag)
16374  {
16375  LogActionError(29, HeadCode, "", EventType, RearName);
16376  }
16377  Utilities->CallLogPop(942);
16378  return(false);
16379  }
16380  if((RearExitPos < 2) && (RearTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit01 > -1))
16381  {
16382  if(ReportFlag)
16383  {
16384  LogActionError(30, HeadCode, "", EventType, RearName);
16385  }
16386  Utilities->CallLogPop(943);
16387  return(false);
16388  }
16389  }
16390  if(FrontType == Bridge)
16391  {
16392  if((FrontEntryPos > 1) && (FrontTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit23 > -1))
16393  {
16394  if(ReportFlag)
16395  {
16396  if(EventType == FailCreateTrain)
16397  {
16398  LogActionError(31, HeadCode, "", EventType, FrontName);
16399  }
16400  else
16401  {
16402  LogActionError(44, HeadCode, "", EventType, RearName);
16403  }
16404  }
16405  Utilities->CallLogPop(944);
16406  return(false);
16407  }
16408  if((FrontEntryPos < 2) && (FrontTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit01 > -1))
16409  {
16410  if(ReportFlag)
16411  {
16412  if(EventType == FailCreateTrain)
16413  {
16414  LogActionError(45, HeadCode, "", EventType, FrontName);
16415  }
16416  else
16417  {
16418  LogActionError(46, HeadCode, "", EventType, RearName);
16419  }
16420  }
16421  Utilities->CallLogPop(945);
16422  return(false);
16423  }
16424  }
16425  EventType = FailCreatePoints; //modified at v2.18.0 so only fails if starting positions conflict with point attribute
16426  if(RearType == Points)
16427  {
16428  if(((RearTrackElement.Attribute == 1) && (RearExitPos == 1)) || ((RearTrackElement.Attribute == 0) && (RearExitPos == 3)))
16429  {
16430  if(ReportFlag)
16431  {
16432  LogActionError(33, HeadCode, "", FailCreatePoints, RearName);
16433  StopTTClockMessage(157, HeadCode + " can't be created, points set wrongly at " + RearName);
16434  }
16435  Utilities->CallLogPop(933);
16436  return(false);
16437  }
16438  }
16439  if(FrontType == Points)
16440  {
16441  if(((FrontTrackElement.Attribute == 1) && (FrontEntryPos == 1)) || ((FrontTrackElement.Attribute == 0) && (FrontEntryPos == 3)))
16442  {
16443  if(ReportFlag)
16444  {
16445  LogActionError(34, HeadCode, "", FailCreatePoints, FrontName);
16446  StopTTClockMessage(158, HeadCode + " can't be created, points set wrongly at " + RearName);
16447  }
16448  Utilities->CallLogPop(934);
16449  return(false);
16450  }
16451  }
16452 
16453  //this section added at v2.9.1 to prevent entry for a train when there's a route set against it
16454  int HLoc = Track->TrackElementAt(1027, RearPosition).HLoc;
16455  int VLoc = Track->TrackElementAt(1028, RearPosition).VLoc;
16456  int ELink = Track->TrackElementAt(1029, RearPosition).Link[RearExitPos]; //if route entry corresponds to RearExitPos then it's set against the train
16457  int RouteNumber; //not used
16458  if(Track->TrackElementAt(1030, RearPosition).TrackType == Continuation)
16459  {
16460  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(8, HLoc, VLoc, ELink, RouteNumber))
16461  {
16462  EventType = FailEntryRouteSetAgainst;
16463  if(ReportFlag)
16464  {
16465  LogActionError(63, HeadCode, "", EventType, RearName);
16466  }
16467  Utilities->CallLogPop(2317);
16468  return(false);
16469  }
16470  }
16471  Utilities->CallLogPop(939);
16472  return(true);
16473 }
16474 
16475 // ---------------------------------------------------------------------------
16476 
16477 AnsiString TTrainController::GetRepeatHeadCode(int Caller, AnsiString BaseHeadCode, int RepeatNumber, int IncDigits)
16478 {
16479  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetRepeatHeadCode," + BaseHeadCode + "," + AnsiString(RepeatNumber) +
16480  "," + AnsiString(IncDigits));
16481  if(!Last2CharactersBothDigits(1, BaseHeadCode) && (IncDigits > 0))
16482  {
16483  throw Exception("Error, last 2 characters not both digits and IncDigits > 0 in GetRepeatHeadCode");
16484  }
16485  if(!Last2CharactersBothDigits(2, BaseHeadCode))
16486  {
16487  Utilities->CallLogPop(1893);
16488  return(BaseHeadCode);
16489  }
16490  int BaseDigits = BaseHeadCode.SubString(3, 2).ToInt();
16491  int NextRepeatDigits = BaseDigits + (IncDigits * RepeatNumber);
16492 
16493  while(NextRepeatDigits >= 100)
16494  {
16495  NextRepeatDigits -= 100; // rolls over after 99
16496  }
16497  AnsiString NextRepeatDigitsStr = AnsiString(NextRepeatDigits);
16498 
16499  if(NextRepeatDigitsStr.Length() < 2)
16500  {
16501  NextRepeatDigitsStr = AnsiString('0') + NextRepeatDigitsStr;
16502  }
16503  AnsiString NextRepeatHeadCode = BaseHeadCode.SubString(1, 2) + NextRepeatDigitsStr;
16504 
16505  Utilities->CallLogPop(1365);
16506  return(NextRepeatHeadCode);
16507 }
16508 
16509 // ---------------------------------------------------------------------------
16510 
16511 TDateTime TTrainController::GetRepeatTime(int Caller, TDateTime BasicTime, int RepeatNumber, int IncMinutes)
16512 {
16513  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetRepeatTime," + AnsiString(double(BasicTime)) + "," +
16514  AnsiString(RepeatNumber) + "," + AnsiString(IncMinutes));
16515  TDateTime NextRepeatTime = BasicTime + TDateTime(((double)(RepeatNumber * IncMinutes)) / 1440.0); // 1440 = no. of minutes in 24h
16516  Utilities->CallLogPop(1366);
16517  return(NextRepeatTime);
16518 }
16519 
16520 // ---------------------------------------------------------------------------
16521 
16522 bool TTrainController::CheckShuttleRepeatTime(int Caller, TDateTime ForwardEventTime, TDateTime ReverseEventTime, int RepeatMinutes)
16523 // For success the ForwardEventTime + repeat time should == ReverseEventTime (allow 10secs either way since converting to doubles)
16524 {
16525  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckShuttleRepeatTime," + AnsiString(double(ForwardEventTime)) + "," +
16526  AnsiString(double(ReverseEventTime)) + "," + AnsiString(RepeatMinutes));
16527  int ForwardSecs = int(double(ForwardEventTime) * 86400);
16528  int ReverseSecs = int(double(ReverseEventTime) * 86400);
16529  int RepeatSecs = RepeatMinutes * 60;
16530 
16531  if((ForwardSecs > (ReverseSecs - RepeatSecs + 10)) || (ForwardSecs < (ReverseSecs - RepeatSecs - 10)))
16532  {
16533  Utilities->CallLogPop(1367);
16534  return(false);
16535  }
16536  else
16537  {
16538  Utilities->CallLogPop(1368);
16539  return(true);
16540  }
16541 }
16542 
16543 // ---------------------------------------------------------------------------
16544 
16545 bool TTrainController::CheckNonRepeatingShuttleLinksAndSetData(int Caller, AnsiString MainHeadCode, AnsiString NonRepeatingHeadCode, bool SetDataAndCheckLocations, bool GiveMessages)
16546 // check for proper non-repeating link cross references and that they have no repeats & that times are consistent, set links if SetDataAndCheckLocations true
16547 
16548 /* Double crosslink (shuttle) table:
16549 Command Format OtherHead NonRepeating- LinkTrain- NonRepeating- Decsription
16550  Code ShuttleLink- EntryPtr ShuttleLink-
16551  HeadCode EntryPtr
16552 
16553 Snt-sh SNTShuttle Y (rtn shuttle) N Y (rtn sh) N Simple shuttle - no feeder service
16554 Frh-sh TimeCmdHeadCode Y (outwd shuttle) N Y (outwd sh) N Simple shuttle - no finishing service
16555 F-nshs FNSNonRepeatToShuttle N (shld be Y for outwd shuttle) Y (shld be N) Y (correct) N (correct) Feeder service link to shuttle
16556 Sns-sh SNSShuttle Y (rtn shuttle) Y (feeder) Y (rtn) Y (fdr) Shuttle link from feeder service
16557 Sns-fsh SNSNonRepeatFromShuttle N (shld be Y for rtn shuttle) Y (shld be N) Y (correct) N (correct) Finishing service link from shuttle
16558 Fns-sh FSHNewService Y (outwd shuttle) Y (finishing) Y (outwd sh) Y (finish) Shuttle link to finishing service
16559 
16560 Note: Any shuttle start can have any finish - feeder and finish, neither, feeder but no finish & vice versa.
16561 */
16562 
16563 {
16564  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckNonRepeatingShuttleLinksAndSetData," + MainHeadCode + "," +
16565  NonRepeatingHeadCode);
16566  int ForwardCount = 0;
16567  int ReverseCount = 0;
16568  unsigned int ForwardTDVectorNumber, ReverseTDVectorNumber;
16569  TActionVectorEntry *ReverseEntryPtr = 0, *ForwardEntryPtr = 0;
16570  // Forward corresponds to Main, Reverse to Other
16571  TTrainDataEntry *MainTrainDataPtr = 0;
16572  TTrainDataEntry *OtherTrainDataPtr = 0;
16573 
16574  // forward check
16575  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
16576  {
16577  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
16578  if(TDEntry.HeadCode == MainHeadCode)
16579  {
16580  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
16581  {
16582  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
16583  if(AVEntry.NonRepeatingShuttleLinkHeadCode == NonRepeatingHeadCode)
16584  {
16585  MainTrainDataPtr = &TrainDataVector.at(x);
16586  ForwardEntryPtr = &AVEntry;
16587  ForwardCount++;
16588  ForwardTDVectorNumber = x;
16589  }
16590  }
16591  }
16592  }
16593  if(ForwardCount == 0)
16594  // this is an exception because the headcodes are selected in the same order as the forward check
16595  {
16596  throw Exception("Error, ForwardCount == 0 in CheckNonRepeatingShuttleLinksAndSetData after called with found values");
16597  }
16598  if(ForwardCount > 1)
16599  {
16600  SecondPassMessage(GiveMessages, "Error in timetable - found more than one reference to " + NonRepeatingHeadCode + " from a train whose headcode is " +
16601  MainHeadCode);
16602  TrainDataVector.clear();
16603  Utilities->CallLogPop(1061);
16604  return(false);
16605  }
16606  // reverse check
16607  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
16608  {
16609  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
16610  if(TDEntry.HeadCode == NonRepeatingHeadCode)
16611  {
16612  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
16613  {
16614  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
16615  if(AVEntry.NonRepeatingShuttleLinkHeadCode == MainHeadCode)
16616  {
16617  OtherTrainDataPtr = &TrainDataVector.at(x);
16618  ReverseCount++;
16619  ReverseEntryPtr = &AVEntry;
16620  ReverseTDVectorNumber = x;
16621  }
16622  }
16623  }
16624  }
16625 
16626  if(ReverseCount == 0)
16627  {
16628  SecondPassMessage(GiveMessages, "Error in timetable - cross reference missing in either " + MainHeadCode + " or " + NonRepeatingHeadCode);
16629  TrainDataVector.clear();
16630  Utilities->CallLogPop(1062);
16631  return(false);
16632  }
16633  if(ReverseCount > 1)
16634  {
16635  SecondPassMessage(GiveMessages, "Error in timetable - found more than one reference to " + MainHeadCode + " from a train whose headcode is " +
16636  NonRepeatingHeadCode);
16637  TrainDataVector.clear();
16638  Utilities->CallLogPop(1063);
16639  return(false);
16640  }
16641  if(((ForwardEntryPtr->Command == "F-nshs") || (ForwardEntryPtr->Command == "Sns-fsh")) && (MainTrainDataPtr->ActionVector.back().FormatType == Repeat))
16642  {
16643  SecondPassMessage(GiveMessages, "Error in timetable - shuttle connecting train " + MainHeadCode + " shouldn't have a repeat");
16644  TrainDataVector.clear();
16645  Utilities->CallLogPop(1064);
16646  return(false);
16647  }
16648  if((ForwardEntryPtr->Command != "F-nshs") && (ForwardEntryPtr->Command != "Sns-fsh") && (MainTrainDataPtr->ActionVector.back().FormatType != Repeat))
16649  {
16650  SecondPassMessage(GiveMessages, "Error in timetable - shuttle train " + MainHeadCode + " does not have a repeat item");
16651  TrainDataVector.clear();
16652  Utilities->CallLogPop(1065);
16653  return(false);
16654  }
16655  if(SetDataAndCheckLocations)
16656  {
16657  if(ForwardEntryPtr->LocationName == "")
16658  {
16659  SecondPassMessage(GiveMessages, "Error in timetable - location error in cross referenced trains " + MainHeadCode + " and " + NonRepeatingHeadCode +
16660  ". One or other service does not have a location set");
16661  TrainDataVector.clear();
16662  Utilities->CallLogPop(1066);
16663  return(false);
16664  }
16665  if(ReverseEntryPtr->LocationName == "")
16666  {
16667  SecondPassMessage(GiveMessages, "Error in timetable - location error in cross referenced trains " + MainHeadCode + " and " + NonRepeatingHeadCode +
16668  ". One or other service does not have a location set");
16669  TrainDataVector.clear();
16670  Utilities->CallLogPop(1067);
16671  return(false);
16672  }
16673  if(ForwardEntryPtr->LocationName != ReverseEntryPtr->LocationName)
16674  {
16675  SecondPassMessage(GiveMessages, "Error in timetable - cross referenced train " + NonRepeatingHeadCode +
16676  " is at a different location to the referencing train " + MainHeadCode);
16677  TrainDataVector.clear();
16678  Utilities->CallLogPop(1068);
16679  return(false);
16680  }
16681  }
16682  if(ForwardEntryPtr->Command == "F-nshs")
16683  // i.e. the non repeating link into the shuttle service
16684  {
16685  if(ForwardEntryPtr->EventTime != ReverseEntryPtr->EventTime)
16686  {
16687  SecondPassMessage(GiveMessages, "Error in timetable - shuttle in-link service " + MainHeadCode +
16688  " finish time not consistent with start time of shuttle service " + NonRepeatingHeadCode);
16689  TrainDataVector.clear();
16690  Utilities->CallLogPop(1069);
16691  return(false);
16692  }
16693  }
16694  if(ForwardEntryPtr->Command == "Fns-sh")
16695  // i.e. the non repeating link out from the shuttle service
16696  {
16697  if(!CheckNonRepeatingShuttleLinkTime(0, ForwardEntryPtr->EventTime, ReverseEntryPtr->EventTime,
16698  MainTrainDataPtr->ActionVector.back().RearStartOrRepeatMins, MainTrainDataPtr->ActionVector.back().NumberOfRepeats))
16699  {
16700  SecondPassMessage(GiveMessages, "Error in timetable - service " + NonRepeatingHeadCode + ", which links out from shuttle service " + MainHeadCode +
16701  ", has the wrong start time. It should correspond to the finish time of the last shuttle.");
16702  TrainDataVector.clear();
16703  Utilities->CallLogPop(1070);
16704  return(false);
16705  }
16706  }
16707  if((ForwardEntryPtr->Command == "F-nshs") || (ForwardEntryPtr->Command == "Sns-fsh"))
16708  // i.e. a non repeating link to or from the shuttle service
16709  {
16710  if(ReverseTDVectorNumber == ForwardTDVectorNumber)
16711  {
16712  SecondPassMessage(GiveMessages, "Error in timetable - the non repeating link service " + NonRepeatingHeadCode +
16713  " appears in the same sequence as the corresponding shuttle service " + MainHeadCode);
16714  TrainDataVector.clear();
16715  Utilities->CallLogPop(1071);
16716  return(false);
16717  }
16718  }
16719 /* it's allowed to have a different description
16720  if((ForwardEntryPtr->Command == "F-nshs") || (ForwardEntryPtr->Command == "Sns-fsh"))//i.e. a non repeating link to or from the shuttle service
16721  {
16722  if((MainTrainDataPtr->Description != "") && (OtherTrainDataPtr->Description != "") && (MainTrainDataPtr->Description != OtherTrainDataPtr->Description))
16723  {
16724  SecondPassMessage(GiveMessages, "Error in timetable - the non repeating link service " + NonRepeatingHeadCode + " has a different description to the corresponding shuttle service " + MainHeadCode);
16725  TrainDataVector.clear();
16726  Utilities->CallLogPop(1072);
16727  return false;
16728  }
16729  }
16730 */
16731  if(ForwardEntryPtr->Command == "Sns-sh")
16732  {
16733  if(ReverseEntryPtr->Command != "F-nshs")
16734  {
16735  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'F-nshs' event for the 'Sns-sh' train whose headcode is " +
16736  MainHeadCode + " and is a new shuttle service formed from the service with headcode " + NonRepeatingHeadCode);
16737  TrainDataVector.clear();
16738  Utilities->CallLogPop(1073);
16739  return(false);
16740  }
16741  }
16742  if(ForwardEntryPtr->Command == "F-nshs")
16743  {
16744  if(ReverseEntryPtr->Command != "Sns-sh")
16745  {
16746  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'Sns-sh' event for the 'F-nshs' train whose headcode is " +
16747  MainHeadCode + " and forms a new shuttle service with headcode " + NonRepeatingHeadCode);
16748  TrainDataVector.clear();
16749  Utilities->CallLogPop(1074);
16750  return(false);
16751  }
16752  else
16753  {
16754  if(SetDataAndCheckLocations)
16755  {
16756  ForwardEntryPtr->LinkedTrainEntryPtr = OtherTrainDataPtr;
16757  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
16758  if(OtherTrainDataPtr->FixedDescription == "") //name changed at v2.16.1
16759  {
16760  OtherTrainDataPtr->FixedDescription = MainTrainDataPtr->FixedDescription;
16761  }
16762  }
16763  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
16764  OtherTrainDataPtr->MaxRunningSpeed = MainTrainDataPtr->MaxRunningSpeed;
16765  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
16766  }
16767  }
16768  if(ForwardEntryPtr->Command == "Sns-fsh")
16769  {
16770  if(ReverseEntryPtr->Command != "Fns-sh")
16771  {
16772  SecondPassMessage(GiveMessages,
16773  "Error in timetable - unable to find a corresponding 'Fns-sh' event for the 'Sns-fsh' non-shuttle service whose headcode is " + MainHeadCode +
16774  " formed from a shuttle service with headcode " + NonRepeatingHeadCode);
16775  TrainDataVector.clear();
16776  Utilities->CallLogPop(1075);
16777  return(false);
16778  }
16779  }
16780  if(ForwardEntryPtr->Command == "Fns-sh")
16781  {
16782  if(ReverseEntryPtr->Command != "Sns-fsh")
16783  {
16784  SecondPassMessage(GiveMessages,
16785  "Error in timetable - unable to find a corresponding 'Sns-fsh' event for the 'Fns-sh' shuttle service whose headcode is " + MainHeadCode +
16786  " and forms a new non-shuttle service with headcode " + NonRepeatingHeadCode);
16787  TrainDataVector.clear();
16788  Utilities->CallLogPop(1076);
16789  return(false);
16790  }
16791  else
16792  {
16793  if(SetDataAndCheckLocations)
16794  {
16795  ForwardEntryPtr->NonRepeatingShuttleLinkEntryPtr = OtherTrainDataPtr;
16796  // links to the non-repeating non-shuttle linked service
16797  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
16798  // needed for creating formatted timetable
16799  if(OtherTrainDataPtr->FixedDescription == "") //name changed at v2.16.1
16800  {
16801  OtherTrainDataPtr->FixedDescription = MainTrainDataPtr->FixedDescription;
16802  }
16803  }
16804  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
16805  OtherTrainDataPtr->MaxRunningSpeed = MainTrainDataPtr->MaxRunningSpeed;
16806  }
16807  }
16808  Utilities->CallLogPop(1077);
16809  return(true);
16810 }
16811 
16812 // ---------------------------------------------------------------------------
16813 
16814 bool TTrainController::CheckNonRepeatingShuttleLinkTime(int Caller, TDateTime ForwardEventTime, TDateTime ReverseEventTime, int RepeatMinutes, int RepeatNumber)
16815 // Forward train is the finish shuttle entry 'Fns-sh'.
16816 // The Reverse (new non-repeating service) time must == Forward time + (RepeatMins * RepeatNumber) but allow 10 secs either side
16817 {
16818  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckNonRepeatingShuttleLinkTime," + AnsiString(double(ForwardEventTime))
16819  + "," + AnsiString(double(ReverseEventTime)) + "," + AnsiString(RepeatMinutes) + "," + AnsiString(RepeatNumber));
16820  int ForwardSecs = int(double(ForwardEventTime) * 86400);
16821  int ReverseSecs = int(double(ReverseEventTime) * 86400);
16822  int RepeatSecs = RepeatMinutes * RepeatNumber * 60;
16823 
16824  if((ReverseSecs > (ForwardSecs + RepeatSecs + 10)) || (ReverseSecs < (ForwardSecs + RepeatSecs - 10)))
16825  {
16826  Utilities->CallLogPop(1369);
16827  return(false);
16828  }
16829  else
16830  {
16831  Utilities->CallLogPop(1370);
16832  return(true);
16833  }
16834 }
16835 
16836 // ---------------------------------------------------------------------------
16837 
16838 bool TTrainController::CheckShuttleServiceIntegrity(int Caller, TTrainDataEntry *TDEntryPtr, bool GiveMessages)
16839 // check that each shuttle start ends either in Fns or Fxx-sh (though a single service can't end in Fxx-sh), and that
16840 // when the Fxx-sh is reached it references the original start and not another shuttle - not allowed to link two shuttles,
16841 // don't ever need to and as designed would skip repeats.
16842 
16843 // enter with TDEntry a shuttle start - Snt-sh or Sns-sh
16844 {
16845  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckShuttleServiceIntegrity," + AnsiString(TDEntryPtr->HeadCode));
16846  if(TDEntryPtr->ActionVector.back().FormatType != Repeat)
16847  {
16848  throw Exception("Error - last entry in " + TDEntryPtr->HeadCode + " service is not a repeat - should have already found this error");
16849  }
16850  TTrainDataEntry *ShuttleStartAddress = TDEntryPtr;
16851  AnsiString OriginalHeadCode = TDEntryPtr->HeadCode;
16852  AnsiString LastActionCommand = (TDEntryPtr->ActionVector.end() - 2)->Command;
16853 
16854  if((LastActionCommand != "Fns") && (LastActionCommand != "Fns-sh") && (LastActionCommand != "Frh-sh"))
16855  {
16856  SecondPassMessage(GiveMessages, "Error in timetable - last event in shuttle service " + TDEntryPtr->HeadCode + " is not 'Fns', 'Fns-sh' or 'Frh-sh'");
16857  TrainDataVector.clear();
16858  Utilities->CallLogPop(1091);
16859  return(false);
16860  }
16861  while(LastActionCommand == "Fns")
16862  {
16863  TDEntryPtr = (TDEntryPtr->ActionVector.end() - 2)->LinkedTrainEntryPtr;
16864  LastActionCommand = (TDEntryPtr->ActionVector.end() - 2)->Command;
16865  if((LastActionCommand != "Fns") && (LastActionCommand != "Fns-sh") && (LastActionCommand != "Frh-sh"))
16866  {
16867  SecondPassMessage(GiveMessages,
16868  "Error in timetable - last event in a continuation shuttle service (i.e links back to a shuttle) whose headcode is " + TDEntryPtr->HeadCode +
16869  " is not 'Fns', 'Fns-sh' or 'Frh-sh'");
16870  TrainDataVector.clear();
16871  Utilities->CallLogPop(1092);
16872  return(false);
16873  }
16874  }
16875  // exit the 'while' with LastActionCommand FSH-XX
16876  if((TDEntryPtr->ActionVector.end() - 2)->LinkedTrainEntryPtr != ShuttleStartAddress)
16877  {
16878  SecondPassMessage(GiveMessages, "Error in timetable - the event that ends service " + TDEntryPtr->HeadCode +
16879  " is a shuttle finish, but it doesn't link back to the start of the original shuttle starting service " + OriginalHeadCode +
16880  ". The linking of two or more shuttles is not permitted.");
16881  TrainDataVector.clear();
16882  Utilities->CallLogPop(1093);
16883  return(false);
16884  }
16885  Utilities->CallLogPop(1094);
16886  return(true);
16887 }
16888 
16889 // ---------------------------------------------------------------------------
16890 
16891 void TTrainController::TimetableMessage(bool GiveMessages, AnsiString Message)
16892 {
16893  if(!GiveMessages)
16894  {
16895  return;
16896  }
16897  // if(ServiceReference == "") ShowMessage(Message);
16898  if(!CheckHeadCodeValidity(12, false, ServiceReference))
16899  {
16900  ShowMessage(ServiceReference + " (not a valid service ref.): " + Message); //amended at v2.15.1 to give information on 'service' so can find it in the list
16901  }
16902  // changed from above at v2.3.0 as a meaningless value for 'Timetable invalid - unable to find a valid start time on its own line' (uses last entry text)
16903  // false means don't give messages within the function
16904  else
16905  {
16906  ShowMessage("Service " + ServiceReference + ": " + Message);
16907  }
16908 }
16909 
16910 // ---------------------------------------------------------------------------
16911 
16912 void TTrainController::SecondPassMessage(bool GiveMessages, AnsiString Message)
16913 {
16914  if(!GiveMessages)
16915  {
16916  return;
16917  }
16918  ShowMessage(Message);
16919 }
16920 
16921 // ---------------------------------------------------------------------------
16922 
16923 AnsiString TTrainController::MinsToAnsiTime(int Input) //added at v2.15.0
16924 {
16925  TrainController->LogEvent("MinsToAnsiTime");
16926  Utilities->CallLog.push_back(Utilities->TimeStamp() + ",MinsToAnsiTime," + Input);
16927  int Mins = Input, Hrs = 0;
16928  while(Mins > 59)
16929  {
16930  Mins -= 60;
16931  Hrs++;
16932  }
16933  AnsiString AnsiMins = AnsiString(Mins);
16934  if(AnsiMins.Length() == 1)
16935  {
16936  AnsiMins = "0" + AnsiMins;
16937  }
16938  AnsiString AnsiHrs = AnsiString(Hrs);
16939  if(AnsiHrs.Length() == 1)
16940  {
16941  AnsiHrs = "0" + AnsiHrs;
16942  }
16943  Utilities->CallLogPop(2577);
16944  return(AnsiHrs + ':' + AnsiMins);
16945 }
16946 
16947 // $$$$$$$$$$$$$$$$$$$$$$$ End of Timetable Functions $$$$$$$$$$$$$$$$$$$$$$$
16948 // ---------------------------------------------------------------------------
16949 
16950 void TTrainController::LogActionError(int Caller, AnsiString HeadCode, AnsiString OtherHeadCode, TActionEventType ActionEventType, AnsiString LocationID)
16951 // FailTrainEntry: 06:00:10 HELD: 2F43 can't enter railway, train obstructing entry position 57-N5
16952 // FailCreateTrain: 06:00:10 HELD: 2F43 can't be created, train obstructing start position 57-N5
16953 // FailCreateLockedRoute: 06:00:10 HELD: 2F43 can't be created on a locked route - start position 57-N5
16954 // FailEnterLockedRoute: 06:00:10 HELD: 2F43 can't enter on a locked route - start position 57-N5
16955 // FailCreatePoints: 06:00:10 HELD: 2F43 can't be created, points set against start position 57-N5
16956 // FailUnexpectedExitRailway: 06:00:10 ERROR: 2F43 left railway unexpectedly at position 57-N5
16957 // FailIncorrectExit: 06:00:10 ERROR: 2F43 left railway at an incorrect exit at position 57-N5
16958 // FailSPAD: 06:00:10 ERROR: 2F43 PASSED SIGNAL AT DANGER at position 57-N5
16959 // FailLockedRoute: 06:00:10 ERROR: SPAD Risk! Signals reset ahead of train, at position 57-N5
16960 // FailLocTooShort: 06:00:10 ERROR: 2F43 failed to split - location too short at Essex Road
16961 // FailSplitDueToOtherTrain: 06:00:10 HELD: 2F43 unable to split - another train is obstructing at Essex Road, please move it if possible
16962 // FailCrashed: 06:00:10: ERROR: 2F43 CRASHED INTO 3F43 at position 46-N7
16963 // FailDerailed: 06:00:10: ERROR: 2F43 DERAILED at position 46-N7
16964 // FailUnexpectedBuffers: 06:00:10: ERROR: 2F43 stopped at buffers unexpectedly at position 46-N7
16965 // FailMissedArrival: 06:00:10: ERROR: 2F43 failed to stop at Essex Road;
16966 // FailMissedSplit: 06:00:10: ERROR: 2F43 failed to split at Essex Road
16967 // FailMissedJBO: 06:00:10: ERROR: 2F43 failed to be joined by join other train at Essex Road
16968 // FailMissedDSC: 06:00:10: ERROR: 2F43 failed to change its description at Essex Road
16969 // FailMissedCMS: 06:00:10: ERROR: 2F43 failed to change its maximum speed at Essex Road
16970 // FailMissedJoinOther: 06:00:10: ERROR: 2F43 failed to join other train at Essex Road
16971 // FailMissedTerminate: 06:00:10: ERROR: 2F43 failed to terminate at Essex Road
16972 // FailMissedNewService: 06:00:10: ERROR: 2F43 failed to form new service at Essex Road
16973 // FailMissedExitRailway: 06:00:10: ERROR: 2F43 failed to exit railway
16974 // FailMissedChangeDirection: 06:00:10: ERROR: 2F43 failed to change direction at Essex Road
16975 // FailMissedPass: 06:00:10: ERROR: 2F43 failed to pass Essex Road
16976 // FailBuffersPreventingStart: 06:00:10: ERROR: 2F43 facing buffers and unable to start at Essex Road
16977 // FailBufferCrash: 06:00:10: ERROR: 2F43 CRASHED INTO BUFFERS at 46-N7
16978 // FailLevelCrossingCrash: 06:00:10: ERROR: 2F43 CRASHED INTO ROAD TRAFFIC AT A LEVEL CROSSING at 46-N7
16979 // RouteForceCancelled: 06:00:10: ERROR: 2F43 forced a route cancellation by occupying it incorrectly at 46-N7
16980 // WaitingForJBO: 06:00:10: WARNING: 2F43 waiting to join 3F43 at Essex Road
16981 // WaitingForFJO: 06:00:10: WARNING: 2F43 waiting to be joined by 3F43 at Essex Road
16982 // FailEntryRouteSetAgainst: 06:00:10: WARNING: 2F43 can't enter railway, route set against it at entry position 57-N5 //added at v2.9.1
16983 // FailNoPowerUnableToDepart: 06:00:10: WARNING: 2F43 is without power so it can't depart from Essex Road // added at v2.19.1
16984 // FailTrainInFront 06:00:10: WARNING: 2F43 can't depart because there is a train in front at Essex Road // added at v2.19.1
16985 
16986 {
16987  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LogActionError," + HeadCode + "," + OtherHeadCode + "," +
16988  AnsiString(ActionEventType) + "," + LocationID);
16989  AnsiString BaseLog = "", Prefix = "", ErrorLog = "", WarningStr = "";
16990 
16991  TDateTime ActualTime = TrainController->TTClockTime; //moved from lower down at v2.9.1
16992  AnsiString TimeAndHeadCode = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode; //added at v2.9.1 to give more info to user
16993 
16994  Prefix = " ERROR: ";
16995  if(ActionEventType == FailTrainEntry)
16996  {
16997  Prefix = " HELD: ";
16998  ErrorLog = " can't enter railway, train obstructing entry position ";
16999  WarningStr = " can't enter railway, train obstructing entry position ";
17000  Display->WarningLog(1, TimeAndHeadCode + WarningStr + LocationID);
17001  }
17002  else if(ActionEventType == FailEntryRouteSetAgainst) //added at v2.9.1
17003  {
17004  Prefix = " HELD: ";
17005  ErrorLog = " can't enter railway, route set against it at entry position ";
17006  WarningStr = " can't enter railway, route set against it at entry position ";
17007  Display->WarningLog(10, TimeAndHeadCode + WarningStr + LocationID);
17008  }
17009  else if(ActionEventType == FailCreateTrain)
17010  {
17011  Prefix = " HELD: ";
17012  ErrorLog = " can't be created, train obstructing at ";
17013  WarningStr = " can't be created, train obstructing at ";
17014  Display->WarningLog(2, TimeAndHeadCode + WarningStr + LocationID);
17015  }
17016  else if(ActionEventType == FailCreateLockedRoute)
17017  {
17018  Prefix = " HELD: ";
17019  ErrorLog = " can't be created on a locked route at ";
17020  WarningStr = " can't be created on a locked route at ";
17021  Display->WarningLog(4, TimeAndHeadCode + WarningStr + LocationID);
17022  }
17023  else if(ActionEventType == FailEnterLockedRoute)
17024  {
17025  Prefix = " HELD: ";
17026  ErrorLog = " can't enter on a locked route at ";
17027  WarningStr = " can't enter on a locked route at ";
17028  Display->WarningLog(5, TimeAndHeadCode + WarningStr + LocationID);
17029  }
17030  else if(ActionEventType == FailCreatePoints)
17031  {
17032  Prefix = " HELD: ";
17033  ErrorLog = " can't be created, points set wrongly at ";
17034  WarningStr = " can't be created, points set wrongly at ";
17035  Display->WarningLog(3, TimeAndHeadCode + WarningStr + LocationID);
17036  }
17037  else if(ActionEventType == FailUnexpectedExitRailway)
17038  {
17039  ErrorLog = " left railway unexpectedly at ";
17040  UnexpectedExits++;
17041  }
17042  else if(ActionEventType == FailIncorrectExit)
17043  {
17044  ErrorLog = " left railway at an incorrect exit at ";
17045  IncorrectExits++;
17046  }
17047  else if(ActionEventType == FailLocTooShort)
17048  {
17049  ErrorLog = " failed to split - location too short at ";
17050  WarningStr = " failed to split, location too short at ";
17051  Display->WarningLog(6, TimeAndHeadCode + WarningStr + LocationID);
17052  }
17053  else if(ActionEventType == FailSplitDueToOtherTrain)
17054  {
17055  Prefix = " HELD: ";
17056  ErrorLog = " unable to split - other train obstructing at ";
17057  WarningStr = " unable to split - other train obstructing at ";
17058  Display->WarningLog(7, TimeAndHeadCode + WarningStr + LocationID);
17059  }
17060  else if(ActionEventType == FailUnexpectedBuffers)
17061  {
17062  ErrorLog = " stopped at buffers unexpectedly at position ";
17063  }
17064  else if(ActionEventType == FailMissedArrival)
17065  {
17066  ErrorLog = " failed to stop at ";
17067  MissedStops++;
17068  }
17069  else if(ActionEventType == FailMissedSplit)
17070  {
17071  ErrorLog = " failed to split at ";
17073  }
17074  else if(ActionEventType == FailMissedJBO)
17075  {
17076  ErrorLog = " failed to be joined by other train at ";
17078  }
17079  else if(ActionEventType == FailMissedDSC) //new at v2.15.0
17080  {
17081  ErrorLog = " failed to change its description at ";
17082 // OtherMissedEvents++; shouldn't count
17083  }
17084  else if(ActionEventType == FailMissedCMS) //new at v2.21.0
17085  {
17086  ErrorLog = " failed to change its maximum speed at ";
17087 // OtherMissedEvents++; shouldn't count
17088  }
17089  else if(ActionEventType == FailMissedJoinOther)
17090  {
17091  ErrorLog = " failed to join other train at ";
17093  }
17094  else if(ActionEventType == FailMissedTerminate)
17095  {
17096  ErrorLog = " failed to terminate at ";
17098  }
17099  else if(ActionEventType == FailMissedNewService)
17100  {
17101  ErrorLog = " failed to form new service at ";
17103  }
17104  else if(ActionEventType == FailMissedExitRailway)
17105  {
17106  ErrorLog = " failed to exit railway ";
17108  }
17109  else if(ActionEventType == FailMissedChangeDirection)
17110  {
17111  ErrorLog = " failed to change direction at ";
17112 // OtherMissedEvents++; //dropped at v2.12.0 as cdt shouldn't count
17113  }
17114  else if(ActionEventType == FailMissedPass)
17115  {
17116  ErrorLog = " failed to pass ";
17117 // OtherMissedEvents++; //dropped at v2.12.0 as missed pass shouldn't count
17118  }
17119  else if(ActionEventType == FailBuffersPreventingStart)
17120  {
17121  ErrorLog = " facing buffers and unable to start at ";
17122  }
17123  else if(ActionEventType == FailDerailed)
17124  {
17125  ErrorLog = " DERAILED at position ";
17126  Prefix = " DERAILMENT: ";
17127  Derailments++;
17128  }
17129  else if(ActionEventType == FailBufferCrash)
17130  {
17131  ErrorLog = " CRASHED INTO BUFFERS at ";
17132  Prefix = " CRASH: ";
17133  CrashedTrains++;
17134  }
17135  else if(ActionEventType == FailLevelCrossingCrash)
17136  {
17137  ErrorLog = " CRASHED INTO ROAD TRAFFIC AT A LEVEL CROSSING at ";
17138  Prefix = " CRASH: ";
17139  CrashedTrains++;
17140  }
17141  else if(ActionEventType == FailCrashed)
17142  {
17143  ErrorLog = " CRASHED INTO " + OtherHeadCode + " at position ";
17144  Prefix = " CRASH: ";
17145  CrashedTrains++;
17146  CrashedTrains++;
17147  }
17148  else if(ActionEventType == FailSPAD)
17149  {
17150  ErrorLog = " PASSED SIGNAL AT DANGER at position ";
17151  Prefix = " SPAD: ";
17152  SPADEvents++;
17153  }
17154  else if(ActionEventType == FailLockedRoute)
17155  {
17156  ErrorLog = "Signals reset ahead of train, route cancelled at position ";
17157  Prefix = " SPAD RISK: ";
17158  SPADRisks++;
17159  }
17160  else if(ActionEventType == RouteForceCancelled)
17161  {
17162  ErrorLog = " forced a route cancellation by occupying it incorrectly at ";
17163  }
17164  else if(ActionEventType == WaitingForJBO)
17165  {
17166  Prefix = " WARNING: ";
17167  ErrorLog = " waiting to join " + OtherHeadCode + " at ";
17168  WarningStr = " waiting to join " + OtherHeadCode + " at ";
17169  Display->WarningLog(8, TimeAndHeadCode + WarningStr + LocationID);
17170  }
17171  else if(ActionEventType == WaitingForFJO)
17172  {
17173  Prefix = " WARNING: ";
17174  ErrorLog = " waiting to be joined by " + OtherHeadCode + " at ";
17175  WarningStr = " waiting to be joined by " + OtherHeadCode + " at ";
17176  Display->WarningLog(9, TimeAndHeadCode + WarningStr + LocationID);
17177  }
17178  else if(ActionEventType == FailNoPowerUnableToDepart) //06:00:10: WARNING: 2F43 is without power so it can't depart from Essex Road // added at v2.19.1
17179  {
17180  Prefix = " WARNING: ";
17181  ErrorLog = " is without power so it can't depart from ";
17182  WarningStr = " is without power so it can't depart from ";
17183  Display->WarningLog(27, TimeAndHeadCode + WarningStr + LocationID);
17184  }
17185  else if(ActionEventType == FailTrainInFront) //06:00:10: WARNING: 2F43 can't depart because there is a train in front at Essex Road // added at v2.19.1
17186  {
17187  Prefix = " WARNING: ";
17188  ErrorLog = " can't depart because there is a train in front at ";
17189  WarningStr = " can't depart because there is a train in front at ";
17190  Display->WarningLog(28, TimeAndHeadCode + WarningStr + LocationID);
17191  }
17192 
17193  BaseLog = Utilities->Format96HHMMSS(ActualTime) + Prefix + HeadCode;
17194  PerfLogForm->PerformanceLog(4, BaseLog + ErrorLog + LocationID);
17195  Utilities->CallLogPop(1371);
17196 }
17197 
17198 // ---------------------------------------------------------------------------
17199 
17201 {
17202 /* //for testing purposes
17203  TrainDataEntry
17204  AnsiString HeadCode, Description;//null on creation
17205  int StartSpeed, MaxRunningSpeed;//both kph
17206  int RepeatNumber;
17207  TActionVector ActionVector;
17208  TTrainOperatingDataVector TrainOperatingDataVector;//no of repeats + 1
17209  TTrainDataEntry() {StartSpeed=0; MaxRunningSpeed=0; RepeatNumber=0;}
17210 
17211  ActionVectorEntry
17212  TTimetableEntryType FormatType;
17213  TDateTime EventTime, ArrivalTime, DepartureTime;//zeroed on creation so change to -1 as a marker for 'not set'
17214  AnsiString LocationName, Command, OtherHeadCode;//null on creation
17215  TActionVectorEntry *OtherHeadCodeStartingEntryPtr;
17216  int RearStartOrRepeatMins, FrontStartOrRepeatDigits;
17217  int RepeatNumber;
17218 
17219  TrainOperatingData
17220  int Mass, MaxBrakeRate, PowerAtRail;//kg;m/s/s;W
17221  int TrainID;
17222  TRunningEntry RunningEntry;
17223  TDateTime StartTime;
17224  AnsiString HeadCode;
17225 */
17226  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SaveTrainDataVectorToFile");
17227  std::ofstream OutFile("TrainData.csv");
17228 
17229  if(OutFile == 0)
17230  {
17231  ShowMessage("Output file TrainData.csv failed to open");
17232  Utilities->CallLogPop(1372);
17233  return;
17234  }
17235  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
17236  {
17237  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
17238  OutFile << "HeadCode" << ',' << "Description" << ',' << "StartSpeed" << ',' << "MaxRunningSpeed" << ',' << "NumberOfTrains" << '\n' << '\n';
17239 
17240  OutFile << TDEntry.HeadCode.c_str() << ',' << TDEntry.FixedDescription.c_str() /* name changed at v2.16.1*/
17241  << ',' << TDEntry.StartSpeed << ',' << TDEntry.MaxRunningSpeed << ',' << TDEntry.NumberOfTrains << '\n' << '\n';
17242 
17243  OutFile << ',' << "FormatType" << ',' << "EventTime" << ',' << "ArrivalTime" << ',' << "DepartureTime" << ',' << "LocationName" << ',' << "Command" <<
17244  ',' << "OtherHeadCode" << ',' << "LinkedTrainEntryPtr" << ',' << "RearStartOrRepeatMins" << ',' << "FrontStartOrRepeatDigits" << ',' <<
17245  "RepeatNumber" << '\n' << '\n';
17246  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
17247  {
17248  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
17249  AnsiString TimetableEntryTypeStr;
17250  // NoFormat, TimeLoc, TimeTimeLoc, TimeCmd, StartNew, TimeCmdHeadCode, FinRemHere, FNSNonRepeatToShuttle, SNTShuttle, SNSShuttle, SNSNonRepeatFromShuttle, FSHNewService, Repeat
17251  switch(AVEntry.FormatType)
17252  {
17253  case 0:
17254  {
17255  TimetableEntryTypeStr = "NoFormat";
17256  break;
17257  }
17258 
17259  case 1:
17260  {
17261  TimetableEntryTypeStr = "TimeLoc";
17262  break;
17263  }
17264 
17265  case 2:
17266  {
17267  TimetableEntryTypeStr = "TimeTimeLoc";
17268  break;
17269  }
17270 
17271  case 3:
17272  {
17273  TimetableEntryTypeStr = "TimeCmd";
17274  break;
17275  }
17276 
17277  case 4:
17278  {
17279  TimetableEntryTypeStr = "StartNew";
17280  break;
17281  }
17282 
17283  case 5:
17284  {
17285  TimetableEntryTypeStr = "TimeCmdHeadCode";
17286  break;
17287  }
17288 
17289  case 6:
17290  {
17291  TimetableEntryTypeStr = "FinRemHere";
17292  break;
17293  }
17294 
17295  case 7:
17296  {
17297  TimetableEntryTypeStr = "FNSShuttle";
17298  break;
17299  }
17300 
17301  case 8:
17302  {
17303  TimetableEntryTypeStr = "SNTShuttle";
17304  break;
17305  }
17306 
17307  case 9:
17308  {
17309  TimetableEntryTypeStr = "SNSShuttle";
17310  break;
17311  }
17312 
17313  case 10:
17314  {
17315  TimetableEntryTypeStr = "SNSNonRepeatFromShuttle";
17316  break;
17317  }
17318 
17319  case 11:
17320  {
17321  TimetableEntryTypeStr = "FSHNewService";
17322  break;
17323  }
17324 
17325  case 12:
17326  {
17327  TimetableEntryTypeStr = "Repeat";
17328  break;
17329  }
17330 
17331  default:
17332  {
17333  TimetableEntryTypeStr = "Default";
17334  break;
17335  }
17336  }
17337  OutFile << ',' << TimetableEntryTypeStr.c_str() << ',' << Utilities->Format96HHMM(AVEntry.EventTime).c_str() << ',' << Utilities->Format96HHMM
17338  (AVEntry.ArrivalTime).c_str() << ',' << Utilities->Format96HHMM(AVEntry.DepartureTime).c_str() << ',' << AVEntry.LocationName.c_str()
17339  << ',' << AVEntry.Command.c_str() << ',' << AVEntry.OtherHeadCode.c_str()
17340  << ',' << AVEntry.LinkedTrainEntryPtr << ',' << AVEntry.RearStartOrRepeatMins << ',' << AVEntry.FrontStartOrRepeatDigits << ',' <<
17341  AVEntry.NumberOfRepeats << '\n';
17342  }
17343  OutFile << '\n';
17344  OutFile << ',' << ',' << "Mass" << ',' << "MaxBrakeRate" << ',' << "PowerAtRail" << ',' << "TrainID" << ',' << "RunningEntry" << '\n' << '\n';
17345  for(unsigned int y = 0; y < TrainDataVector.at(x).TrainOperatingDataVector.size(); y++)
17346  {
17347  TTrainOperatingData TOD = TrainDataVector.at(x).TrainOperatingDataVector.at(y);
17348  AnsiString RunningEntryStr;
17349  // NotStarted, Running, Exited
17350  switch(TOD.RunningEntry)
17351  {
17352  case 0:
17353  {
17354  RunningEntryStr = "NotStarted";
17355  break;
17356  }
17357 
17358  case 1:
17359  {
17360  RunningEntryStr = "Running";
17361  break;
17362  }
17363 
17364  case 2:
17365  {
17366  RunningEntryStr = "Exited";
17367  break;
17368  }
17369  }
17370  OutFile << ',' << ',' << TOD.TrainID << ',' << RunningEntryStr.c_str() << ',' << '\n';
17371  }
17372  OutFile << '\n';
17373  }
17374  OutFile.close();
17375  Utilities->CallLogPop(1373);
17376 }
17377 
17378 // ---------------------------------------------------------------------------
17379 
17380 void TTrainController::StopTTClockMessage(int Caller, AnsiString Message)
17381 // ShowMessage stops everything so this function used where a message is needed when may be in Operating mode.
17382 // The timetable Restart and BaseTimes are reset so the timetable clock stops & restarts when 'OK' button pressed (in ClockTimer2 when StopTTClockFlag is false)
17383 {
17384  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",StopTTClockMessage," + Message);
17385  StopTTClockFlag = true; // so TTClock stopped during MasterClockTimer function
17387  ShowMessage(Message);
17388  BaseTime = TDateTime::CurrentDateTime();
17389  StopTTClockFlag = false;
17390  Utilities->CallLogPop(1374);
17391 }
17392 
17393 // ---------------------------------------------------------------------------
17394 
17395 void TTrainController::SaveSessionTrains(int Caller, std::ofstream &SessionFile)
17396 // save *TrainDataEntryPtr & *ActionVectorEntryPtr as integer offsets
17397 // from the start of the relevant vectors. Can't save the pointer values
17398 // as these will be different each time the vectors are created
17399 {
17400  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SaveSessionTrains");
17401  Utilities->SaveFileInt(SessionFile, TrainVector.size());
17402  for(unsigned int x = 0; x < TrainVector.size(); x++)
17403  {
17404  TrainVectorAt(55, x).SaveOneSessionTrain(0, SessionFile);
17405  }
17406  Utilities->CallLogPop(1375);
17407 }
17408 
17409 // ---------------------------------------------------------------------------
17410 
17411 void TTrainController::LoadSessionTrains(int Caller, std::ifstream &SessionFile)
17412 {
17413  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LoadSessionTrains");
17414  int NumberOfTrains = Utilities->LoadFileInt(SessionFile);
17415  TTrain *NewTrain = new TTrain(1, 0, 0, "", 0, 1, 0, 0, 0, (TTrainMode)0, 0, 0, 0, 0, 0); // have to have >0 for mass, else have divide
17416  // by zero error in calculating AValue, use 1
17417  for(int x = 0; x < NumberOfTrains; x++)
17418  {
17419  *NewTrain = TTrain(2, 0, 0, "", 0, 1, 0, 0, 0, (TTrainMode)0, 0, 0, 0, 0, 0); // have to have >0 for mass, else have divide
17420  // by zero error in calculating AValue, use 1
17421  NewTrain->LoadOneSessionTrain(0, SessionFile);
17422  if((NewTrain->EntrySpeed < 1) && (NewTrain->PowerAtRail < 1))
17423  // added at v2.4.0. have to include as that value not stored in session file
17424  {
17425  NewTrain->StoppedWithoutPower = true;
17426  }
17427  TrainVector.push_back(*NewTrain);
17428  LastTrainLoaded = x;
17429  }
17430  delete NewTrain;
17431  Utilities->CallLogPop(1376);
17432 }
17433 
17434 // ---------------------------------------------------------------------------
17435 
17436 bool TTrainController::CheckSessionTrains(int Caller, std::ifstream &InFile)
17437 {
17438  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckSessionTrains");
17439  int NumberOfTrains;
17440 
17441  if(!Utilities->CheckAndReadFileInt(InFile, 0, 10000, NumberOfTrains))
17442  {
17443  Utilities->CallLogPop(1377);
17444  return(false);
17445  }
17446  for(int x = 0; x < NumberOfTrains; x++)
17447  {
17448  if(!(TTrain::CheckOneSessionTrain(InFile)))
17449  {
17450  Utilities->CallLogPop(1378);
17451  return(false);
17452  }
17453  }
17454  Utilities->CallLogPop(1379);
17455  return(true);
17456 }
17457 
17458 // ---------------------------------------------------------------------------
17459 
17460 void TTrainController::SaveSessionLockedRoutes(int Caller, std::ofstream &SessionFile)
17461 {
17462  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SaveSessionLockedRoutes");
17463  Utilities->SaveFileInt(SessionFile, AllRoutes->LockedRouteVector.size());
17464  for(unsigned int x = 0; x < AllRoutes->LockedRouteVector.size(); x++)
17465  {
17466  Utilities->SaveFileInt(SessionFile, AllRoutes->LockedRouteVector.at(x).RouteNumber);
17467  Utilities->SaveFileInt(SessionFile, AllRoutes->LockedRouteVector.at(x).RearTrackVectorPosition);
17468  Utilities->SaveFileInt(SessionFile, AllRoutes->LockedRouteVector.at(x).LastTrackVectorPosition);
17469  Utilities->SaveFileInt(SessionFile, AllRoutes->LockedRouteVector.at(x).LastXLinkPos);
17470  Utilities->SaveFileDouble(SessionFile, double(AllRoutes->LockedRouteVector.at(x).LockStartTime));
17471  }
17472  Utilities->CallLogPop(1380);
17473 }
17474 
17475 // ---------------------------------------------------------------------------
17476 
17477 void TTrainController::LoadSessionLockedRoutes(int Caller, std::ifstream &SessionFile)
17478 {
17479  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LoadSessionLockedRoutes");
17480  TAllRoutes::TLockedRouteClass LockedRouteObject;
17481  int LockedRouteVectorSize = Utilities->LoadFileInt(SessionFile);
17482 
17483  for(int x = 0; x < LockedRouteVectorSize; x++)
17484  {
17485  LockedRouteObject.RouteNumber = Utilities->LoadFileInt(SessionFile);
17486  LockedRouteObject.RearTrackVectorPosition = Utilities->LoadFileInt(SessionFile);
17487  LockedRouteObject.LastTrackVectorPosition = Utilities->LoadFileInt(SessionFile);
17488  LockedRouteObject.LastXLinkPos = Utilities->LoadFileInt(SessionFile);
17489  double LockStartTimeDouble = Utilities->LoadFileDouble(SessionFile);
17490  LockedRouteObject.LockStartTime = TDateTime(LockStartTimeDouble);
17491  AllRoutes->LockedRouteVector.push_back(LockedRouteObject);
17492  }
17493  Utilities->CallLogPop(1381);
17494 }
17495 
17496 // ---------------------------------------------------------------------------
17497 
17498 bool TTrainController::CheckSessionLockedRoutes(int Caller, std::ifstream &SessionFile)
17499 {
17500  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckSessionLockedRoutes");
17501  int LockedRouteVectorSize;
17502 
17503  if(!Utilities->CheckAndReadFileInt(SessionFile, 0, 10000, LockedRouteVectorSize))
17504  {
17505  Utilities->CallLogPop(1382);
17506  return(false);
17507  }
17508  for(int x = 0; x < LockedRouteVectorSize; x++)
17509  {
17510  if(!Utilities->CheckFileInt(SessionFile, 0, 1000000))
17511  {
17512  Utilities->CallLogPop(1383);
17513  return(false);
17514  }
17515  if(!Utilities->CheckFileInt(SessionFile, 0, 1000000))
17516  {
17517  Utilities->CallLogPop(1384);
17518  return(false);
17519  }
17520  if(!Utilities->CheckFileInt(SessionFile, 0, 1000000))
17521  {
17522  Utilities->CallLogPop(1385);
17523  return(false);
17524  }
17525  if(!Utilities->CheckFileInt(SessionFile, 0, 3))
17526  {
17527  Utilities->CallLogPop(1386);
17528  return(false);
17529  }
17530  if(!Utilities->CheckFileDouble(SessionFile))
17531  {
17532  Utilities->CallLogPop(1387);
17533  return(false);
17534  }
17535  }
17536  Utilities->CallLogPop(1388);
17537  return(true);
17538 }
17539 
17540 // ---------------------------------------------------------------------------
17541 
17542 void TTrainController::SaveSessionContinuationAutoSigEntries(int Caller, std::ofstream &SessionFile)
17543 {
17544  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SaveSessionContinuationAutoSigEntries");
17545  Utilities->SaveFileInt(SessionFile, ContinuationAutoSigVector.size());
17546  for(unsigned int x = 0; x < ContinuationAutoSigVector.size(); x++)
17547  {
17548  Utilities->SaveFileInt(SessionFile, ContinuationAutoSigVector.at(x).RouteNumber);
17549  Utilities->SaveFileInt(SessionFile, ContinuationAutoSigVector.at(x).AccessNumber);
17550  Utilities->SaveFileDouble(SessionFile, ContinuationAutoSigVector.at(x).FirstDelay);
17551  Utilities->SaveFileDouble(SessionFile, ContinuationAutoSigVector.at(x).SecondDelay);
17552  Utilities->SaveFileDouble(SessionFile, ContinuationAutoSigVector.at(x).ThirdDelay);
17553  Utilities->SaveFileDouble(SessionFile, double(ContinuationAutoSigVector.at(x).PassoutTime));
17554  }
17555  Utilities->CallLogPop(1389);
17556 }
17557 
17558 // ---------------------------------------------------------------------------
17559 
17560 void TTrainController::LoadSessionContinuationAutoSigEntries(int Caller, std::ifstream &SessionFile)
17561 {
17562  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LoadSessionContinuationAutoSigEntries");
17563  TContinuationAutoSigEntry ContinuationAutoSigObject;
17564  int ContinuationAutoSigVectorSize = Utilities->LoadFileInt(SessionFile);
17565 
17566  for(int x = 0; x < ContinuationAutoSigVectorSize; x++)
17567  {
17568  ContinuationAutoSigObject.RouteNumber = Utilities->LoadFileInt(SessionFile);
17569  ContinuationAutoSigObject.AccessNumber = Utilities->LoadFileInt(SessionFile);
17570  ContinuationAutoSigObject.FirstDelay = Utilities->LoadFileDouble(SessionFile);
17571  ContinuationAutoSigObject.SecondDelay = Utilities->LoadFileDouble(SessionFile);
17572  ContinuationAutoSigObject.ThirdDelay = Utilities->LoadFileDouble(SessionFile);
17573  double PassoutTimeDouble = Utilities->LoadFileDouble(SessionFile);
17574  ContinuationAutoSigObject.PassoutTime = TDateTime(PassoutTimeDouble);
17575  ContinuationAutoSigVector.push_back(ContinuationAutoSigObject);
17576  }
17577  Utilities->CallLogPop(1390);
17578 }
17579 
17580 // ---------------------------------------------------------------------------
17581 
17582 bool TTrainController::CheckSessionContinuationAutoSigEntries(int Caller, std::ifstream &SessionFile)
17583 {
17584  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckSessionContinuationAutoSigEntries");
17585  int ContinuationAutoSigVectorSize;
17586 
17587  if(!Utilities->CheckAndReadFileInt(SessionFile, 0, 10000, ContinuationAutoSigVectorSize))
17588  {
17589  Utilities->CallLogPop(1391);
17590  return(false);
17591  }
17592  for(int x = 0; x < ContinuationAutoSigVectorSize; x++)
17593  {
17594  if(!Utilities->CheckFileInt(SessionFile, 0, 1000000))
17595  {
17596  Utilities->CallLogPop(1392);
17597  return(false);
17598  }
17599  if(!Utilities->CheckFileInt(SessionFile, 0, 3))
17600  {
17601  Utilities->CallLogPop(1393);
17602  return(false);
17603  }
17604  if(!Utilities->CheckFileDouble(SessionFile))
17605  {
17606  Utilities->CallLogPop(1405);
17607  return(false);
17608  }
17609  if(!Utilities->CheckFileDouble(SessionFile))
17610  {
17611  Utilities->CallLogPop(1406);
17612  return(false);
17613  }
17614  if(!Utilities->CheckFileDouble(SessionFile))
17615  {
17616  Utilities->CallLogPop(1407);
17617  return(false);
17618  }
17619  if(!Utilities->CheckFileDouble(SessionFile))
17620  {
17621  Utilities->CallLogPop(1394);
17622  return(false);
17623  }
17624  }
17625  Utilities->CallLogPop(1395);
17626  return(true);
17627 }
17628 
17629 // ---------------------------------------------------------------------------
17630 
17631 /*
17632  class TContinuationTrainExpectationEntry //for expected trains at continuation entries
17633  {
17634  public:
17635  AnsiString Description; ///< service description
17636  AnsiString HeadCode; ///< service headcode
17637  int RepeatNumber; ///< service RepeatNumber
17638  int IncrementalMinutes; ///< Repeat separation in minutes
17639  int IncrementalDigits; ///< Repeat headcode separation
17640  int VectorPosition; ///< TrackVectorPosition for the continuation element
17641  TTrainDataEntry *TrainDataEntryPtr; ///< points to the service entry in the timetable's TrainDataVector
17642  };
17643 
17644 
17645  typedef std::multimap<TDateTime,TContinuationTrainExpectationEntry> TContinuationTrainExpectationMultiMap;
17646  typedef pair<TDateTime, TContinuationTrainExpectationEntry> TContinuationTrainExpectationMultiMapPair;
17647 */
17648 
17650 // build this into timetable load so session loading can use it too
17651 // being a multimap it automatically sorts in ascending EventTime order
17652 {
17653  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",BuildContinuationTrainExpectationMultiMap");
17655  // need to clear as this called twice when load a session
17656  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
17657  {
17658  TTrainDataEntry & TDEntry = TrainDataVector.at(x);
17659  const TActionVectorEntry &AVFirstEntry = TDEntry.ActionVector.at(0);
17660  const TActionVectorEntry &AVLastEntry = TDEntry.ActionVector.at(TDEntry.ActionVector.size() - 1);
17661 
17662  if(AVFirstEntry.Command == "Snt")
17663  // new train (no need to include Snt-sh since they can't start at a continuation)
17664  {
17667  {
17669  CTEEntry.VectorPosition = AVFirstEntry.RearStartOrRepeatMins;
17670  // retains this value for all repeats
17671  CTEEntry.RepeatNumber = 0; // for first entry
17672  CTEEntry.TrainDataEntryPtr = &TDEntry;
17673  // retains this value for all repeats
17674  CTEEntry.HeadCode = TDEntry.HeadCode;
17675  CTEEntry.FixedDescription = TDEntry.FixedDescription; //name changed at v2.16.1
17676  CTEEntry.IncrementalMinutes = 0;
17677  CTEEntry.IncrementalDigits = 0;
17678  if(AVLastEntry.FormatType == Repeat)
17679  {
17680  CTEEntry.IncrementalMinutes = AVLastEntry.RearStartOrRepeatMins;
17681  // retains this value or 0 for all repeats
17682  CTEEntry.IncrementalDigits = AVLastEntry.FrontStartOrRepeatDigits;
17683  // retains this value or 0 for all repeats
17684  }
17685  CTEMMP.first = AVFirstEntry.EventTime;
17686  CTEMMP.second = CTEEntry;
17687  ContinuationTrainExpectationMultiMap.insert(CTEMMP);
17688  // base entry
17689  if(TDEntry.NumberOfTrains > 1)
17690  {
17691  if(AVLastEntry.FormatType != Repeat)
17692  {
17693  throw Exception("Error, Last ActionVectorEntry not a repeat in BuildContinuationTrainExpectationMultiMap");
17694  }
17695  for(int y = 1; y < TDEntry.NumberOfTrains; y++)
17696  {
17697  CTEEntry.RepeatNumber = y;
17698  CTEEntry.HeadCode = GetRepeatHeadCode(23, TDEntry.HeadCode, y, AVLastEntry.FrontStartOrRepeatDigits);
17699  // CTEEntry.VectorPosition stays same
17700  CTEMMP.first = GetRepeatTime(3, AVFirstEntry.EventTime, y, AVLastEntry.RearStartOrRepeatMins);
17701  CTEMMP.second = CTEEntry;
17702  ContinuationTrainExpectationMultiMap.insert(CTEMMP);
17703  }
17704  }
17705  }
17706  }
17707  }
17708  Utilities->CallLogPop(1396);
17709 }
17710 
17711 // ---------------------------------------------------------------------------
17712 
17714 {
17715  // called when WarningFlashCount == 0 or when press zoomout button
17716  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotTrainsInZoomOutMode");
17717  if(!Display->ZoomOutFlag)
17718  {
17719  Utilities->CallLogPop(1156);
17720  return;
17721  }
17722  for(unsigned int x = 0; x < TrainVector.size(); x++)
17723  {
17724  // plot blanks & track for all train, even if to be overplotted, since when flashing need to overplot all anyway
17725  // if OldPlotElement[x] == -1 then ignore (not plotted)
17727  TrainVectorAt(57, x).PlotTrainInZoomOutMode(0, Flash);
17728  }
17729  Display->Update();
17730  // need to keep this since Update() not called for PlotSmallOutput as too slow
17731  Utilities->CallLogPop(742);
17732 }
17733 
17734 // ---------------------------------------------------------------------------
17735 
17736 TTrain &TTrainController::TrainVectorAt(int Caller, int VecPos)
17737 {
17738  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainVectorAt," + AnsiString(VecPos));
17739  if((VecPos < 0) || (VecPos >= (int)TrainVector.size()))
17740  {
17741  throw Exception("Out of Range Error, vector size: " + AnsiString(TrainVector.size()) + ", VecPos: " + AnsiString(VecPos) + " in TrainVectorAt");
17742  }
17743  Utilities->CallLogPop(740);
17744  return(TrainVector.at(VecPos));
17745 }
17746 
17747 // ---------------------------------------------------------------------------
17748 
17749 void TTrainController::CreateFormattedTimetable(int Caller, AnsiString RailwayTitle, AnsiString TimetableTitle, AnsiString CurDir)
17750 {
17751  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CreateFormattedTimetable");
17752  AnsiString RetStr = "", PartStr = "";
17753 
17754 
17755 /*
17756  Have description & mass etc for train at top - header, then array of actions
17757 
17758  class TActionVectorEntry
17759  {
17760  public:
17761  AnsiString LocationName, Command, OtherHeadCode, NonRepeatingShuttleLinkHeadCode;
17763  bool SignallerControl;
17765  bool Warning;
17767  int NumberOfRepeats;
17769  int RearStartOrRepeatMins, FrontStartOrRepeatDigits;
17771  TDateTime EventTime, ArrivalTime, DepartureTime;
17773  TNumList ExitList;
17775  TTimetableFormatType FormatType;
17777  TTimetableLocationType LocationType;
17779  TTimetableSequenceType SequenceType;
17781  TTimetableShuttleLinkType ShuttleLinkType;
17783  TTrainDataEntry *LinkedTrainEntryPtr;
17785  TTrainDataEntry *NonRepeatingShuttleLinkEntryPtr;
17787 
17788  typedef std::vector<TActionVectorEntry> TActionVector;//contains all actions for a single train
17789 
17790  enum TRunningEntry {NotStarted, Running, Exited};//contains status info for each train
17791 
17792  class TTrainOperatingData
17793  {
17794  public:
17795  int TrainID;
17796  TActionEventType EventReported;
17797  TRunningEntry RunningEntry;
17798 
17799  //inline function
17800  TTrainOperatingData() {TrainID = -1; EventReported= NoEvent; RunningEntry=NotStarted;}//ID -1 = marker for not running
17801  };
17802 
17803  typedef std::vector<TTrainOperatingData> TTrainOperatingDataVector;
17804 
17805  class TTrainDataEntry
17806  {
17807  public:
17808  AnsiString HeadCode, ServiceReference, Description;
17810  double MaxBrakeRate;
17812  double MaxRunningSpeed;
17814  double PowerAtRail;
17816  int Mass;
17818  int NumberOfTrains;
17820  int SignallerSpeed;
17822  int StartSpeed;
17824  TActionVector ActionVector;
17826  TTrainOperatingDataVector TrainOperatingDataVector;
17828 
17829  //inline function
17830  TTrainDataEntry() {StartSpeed=0; MaxRunningSpeed=0; NumberOfTrains=0;}
17831  };
17832 
17833  typedef std::vector<TTrainDataEntry> TTrainDataVector;//object is a member of TTrainController & contains the whole timetable
17834 
17835  //formatted timetable types
17836  class TOneTrainFormattedEntry
17837  {
17838  AnsiString Action;//includes location if relevanr
17839  AnsiString Time;
17840  };
17841 
17842  typedef std::vector<TOneTrainFormattedEntry> TOneFormattedTrainVector;
17843 
17844  class TOneCompleteFormattedTrain//headcode + list of actions
17845  {
17846  public:
17847  AnsiString HeadCode;
17848  TOneFormattedTrainVector OneFormattedTrainVector;
17849  };
17850 
17851  typedef std::vector<TOneCompleteFormattedTrain> TOneCompleteFormattedTrainVector;//list af all repeats
17852 
17853  class TTrainFormattedInformation//contains all information for a single TT entry (including repeats)
17854  {
17855  public:
17856  AnsiString Header;//description, mass, power, brake rate etc
17857  int NumberOfTrains;// number of repeats + 1
17858  TOneCompleteFormattedTrainVector OneCompleteFormattedTrainVector;//list af all repeats
17859  };
17860 
17861 
17862  typedef std::vector<TTrainFormattedInformation> TAllFormattedTrains;//all timetable in formatted form
17863  //end of formatted timetable types
17864 
17865 */
17866 
17867  AnsiString TTFileName = TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss");
17868 
17869  // format "16/06/2009 20:55:17"
17870  // avoid characters in filename:= / \ : * ? " < > |
17871  TTFileName = CurDir + "\\Formatted timetables\\Timetable " + TTFileName + "; " + RailwayTitle + "; " + TimetableTitle + ".csv";
17872 
17873  AnsiString ShortTTName = "";
17874 
17875  for(int x = TTFileName.Length(); x > 0; x--)
17876  {
17877  if(TTFileName[x] == '\\')
17878  {
17879  ShortTTName = TTFileName.SubString(x + 1, TTFileName.Length() - x - 4);
17880  break;
17881  }
17882  }
17883 
17884  ShowMessage("Creates two timetables named " + ShortTTName +
17885  " in the 'Formatted timetables' folder, one in service order in '.csv' format, and one in chronological order in '.txt' format");
17886 
17887  Screen->Cursor = TCursor(-11); // Hourglass
17888 
17889  AnsiString FormatNoDPStr = "#######0";
17890  AnsiString TableTitle = "", TimetableTimeStr = "", MassStr = "", PowerStr = "", BrakeStr = "", MaxSpeedStr = "", FirstHeadCode = "", Header = "";
17891 
17893  TableTitle = "Railway: " + RailwayTitle + "; Timetable: " + TimetableTitle + "; Start time: " + TimetableTimeStr;
17894  TAllFormattedTrains *AllTTTrains = new TAllFormattedTrains;
17895 
17896  // all timetable in formatted form
17897  //create the AllTTTrains vector
17898  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
17899  {
17900  MassStr = "", PowerStr = "", BrakeStr = "", MaxSpeedStr = "";
17901  const TTrainDataEntry &TrainDataEntry = TrainDataVector.at(x);
17902  if(TrainDataEntry.Mass > 0)
17903  {
17904  MassStr = "; Mass " + AnsiString::FormatFloat(FormatNoDPStr, ((double)TrainDataEntry.Mass) / 1000) + "Te; ";
17905  }
17906  if(TrainDataEntry.PowerAtRail > 0)
17907  {
17908  PowerStr = "Power " + AnsiString::FormatFloat(FormatNoDPStr, TrainDataEntry.PowerAtRail / 1000 / 0.8) + "kW; ";
17909  }
17910  if(TrainDataEntry.MaxBrakeRate > 0)
17911  {
17912  BrakeStr = "Brake force " + AnsiString::FormatFloat(FormatNoDPStr, (TrainDataEntry.MaxBrakeRate * TrainDataEntry.Mass / 9810)) + "Te; ";
17913  }
17914  if(TrainDataEntry.MaxRunningSpeed > 0)
17915  {
17916  MaxSpeedStr = "Maximum speed " + AnsiString::FormatFloat(FormatNoDPStr, TrainDataEntry.MaxRunningSpeed) + " km/h";
17917  }
17918  FirstHeadCode = TrainDataEntry.HeadCode;
17919  int IncDigits = 0, IncMinutes = 0;
17920  const TActionVector &ActionVector = TrainDataEntry.ActionVector;
17921  if(!ActionVector.empty())
17922  {
17923  if(ActionVector.at(ActionVector.size() - 1).FormatType == Repeat)
17924  {
17925  IncDigits = ActionVector.at(ActionVector.size() - 1).FrontStartOrRepeatDigits;
17926  IncMinutes = ActionVector.at(ActionVector.size() - 1).RearStartOrRepeatMins;
17927  }
17928  }
17929  TTrainFormattedInformation OneTTLine;
17930  // contains all information for a single TT entry (including repeats)
17931  for(int y = 0; y < TrainDataEntry.NumberOfTrains; y++)
17932  {
17933  OneTTLine.Header = "";
17934  if((TrainDataEntry.FixedDescription != "") && (MassStr != "")) //name changed at v2.16.1
17935  {
17936  OneTTLine.Header = TrainDataEntry.FixedDescription + MassStr + PowerStr + BrakeStr + MaxSpeedStr; //name changed at v2.16.1
17937  }
17938  else if(TrainDataEntry.FixedDescription != "") //name changed at v2.16.1
17939  {
17940  OneTTLine.Header = TrainDataEntry.FixedDescription; //name changed at v2.16.1
17941  }
17942  OneTTLine.NumberOfTrains = TrainDataEntry.NumberOfTrains;
17943  TOneCompleteFormattedTrain OneTTTrain; // headcode + list of actions
17944  for(unsigned int z = 0; z < ActionVector.size(); z++)
17945  {
17946  TOneTrainFormattedEntry OneTTEntry;
17947  OneTTTrain.HeadCode = GetRepeatHeadCode(24, FirstHeadCode, y, IncDigits);
17948  TActionVectorEntry ActionVectorEntry = ActionVector.at(z);
17949  AnsiString PartStr = "", TimeStr = "";
17950 /*
17951  enum TTimetableFormatType {NoFormat, TimeLoc, TimeTimeLoc, TimeCmd, StartNew, TimeCmdHeadCode, FinRemHere,
17952  FNSNonRepeatToShuttle, SNTShuttle, SNSShuttle, SNSNonRepeatFromShuttle, FSHNewService, Repeat, PassTime,
17953  ExitRailway};
17954  enum TTimetableSequenceType {NoSequence, StartSequence, FinishSequence, IntermediateSequence, SequTypeForRepeatEntry};
17955  enum TTimetableLocationType {NoLocation, AtLocation, EnRoute, LocTypeForRepeatEntry};
17956  enum TTimetableShuttleLinkType {NoShuttleLink, NotAShuttleLink, ShuttleLink, ShuttleLinkTypeForRepeatEntry};
17957 */
17958  if(ActionVectorEntry.SequenceType == StartSequence)
17959  {
17960  if(ActionVectorEntry.FormatType == StartNew)
17961  {
17962  if(ActionVectorEntry.LocationName != "")
17963  {
17964  if(Track->TrackElementAt(742, ActionVectorEntry.RearStartOrRepeatMins).TrackType == Continuation)
17965  {
17966  PartStr = "Enters at " + ActionVectorEntry.LocationName;
17967  }
17968  else
17969  {
17970  PartStr = "Created at " + ActionVectorEntry.LocationName;
17971  }
17972  }
17973  else // may be a named continuation or other element, and if so report that
17974  {
17975  AnsiString LocName = Track->TrackElementAt(739, ActionVectorEntry.RearStartOrRepeatMins).ActiveTrackElementName;
17976  if(Track->TrackElementAt(740, ActionVectorEntry.RearStartOrRepeatMins).TrackType == Continuation)
17977  {
17978  if(LocName != "")
17979  {
17980  PartStr = "Enters at " + LocName;
17981  }
17982  else // use rear position if it's a continuation
17983  {
17984  PartStr = "Enters at " + Track->TrackElementAt(737, ActionVectorEntry.RearStartOrRepeatMins).ElementID;
17985  }
17986  }
17987  else // not a continuation
17988  {
17989  if(LocName != "")
17990  // if not a continuation then LocName should be same as ActionVectorEntry.LocationName
17991  // but include anyway
17992  {
17993  PartStr = "Created at " + LocName;
17994  }
17995  else // use rear position again
17996  {
17997  PartStr = "Created at " + Track->TrackElementAt(741, ActionVectorEntry.RearStartOrRepeatMins).ElementID;
17998  }
17999  }
18000  }
18001  TimeStr = Utilities->Format96HHMM(GetRepeatTime(20, ActionVectorEntry.EventTime, y, IncMinutes));
18002  }
18003  else if(ActionVectorEntry.FormatType == SNTShuttle)
18004  {
18005  if(y == 0) // first train
18006  {
18007  PartStr = "Enters at " + ActionVectorEntry.LocationName;
18008  TimeStr = Utilities->Format96HHMM(GetRepeatTime(21, ActionVectorEntry.EventTime, y, IncMinutes));
18009  }
18010  else
18011  {
18012  PartStr = "Repeat shuttle service at " + ActionVectorEntry.LocationName + " from ";
18013  TimeStr = GetRepeatHeadCode(45, ActionVectorEntry.OtherHeadCode, y - 1, IncDigits) + " at " +
18014  Utilities->Format96HHMM(GetRepeatTime(26, ActionVectorEntry.EventTime, y, IncMinutes));
18015  } // y-1 for headcode above since it is the last repeat value that the train is from
18016 
18017  }
18018  else if(ActionVectorEntry.Command == "Sfs")
18019  {
18020  PartStr = "New service at " + ActionVectorEntry.LocationName + " split from";
18021  TimeStr = GetRepeatHeadCode(33, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
18022  Utilities->Format96HHMM(GetRepeatTime(24, ActionVectorEntry.EventTime, y, IncMinutes));
18023  }
18024  else if(ActionVectorEntry.Command == "Sns")
18025  {
18026  PartStr = "New service at " + ActionVectorEntry.LocationName + " from";
18027  TimeStr = GetRepeatHeadCode(34, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
18028  Utilities->Format96HHMM(GetRepeatTime(25, ActionVectorEntry.EventTime, y, IncMinutes));
18029  }
18030  else if(ActionVectorEntry.FormatType == SNSShuttle)
18031  {
18032  if(y == 0) // first entry from shuttle
18033  {
18034  PartStr = "New service at " + ActionVectorEntry.LocationName + " from";
18035  TimeStr = ActionVectorEntry.NonRepeatingShuttleLinkHeadCode + " at " +
18036  Utilities->Format96HHMM(GetRepeatTime(27, ActionVectorEntry.EventTime, y, IncMinutes));
18037  }
18038  else
18039  {
18040  PartStr = "Repeat shuttle service at " + ActionVectorEntry.LocationName + " from ";
18041  TimeStr = GetRepeatHeadCode(35, ActionVectorEntry.OtherHeadCode, y - 1, IncDigits) + " at " +
18042  Utilities->Format96HHMM(GetRepeatTime(22, ActionVectorEntry.EventTime, y, IncMinutes));
18043  } // y-1 for headcode above since it is the last repeat value that the train is from
18044 
18045  }
18046  else if(ActionVectorEntry.FormatType == SNSNonRepeatFromShuttle)
18047  {
18048  PartStr = "New service at " + ActionVectorEntry.LocationName + " from";
18049  // need repeat for the non-repeating headcode as it's the last train of the repeating shuttle
18050  TTrainDataEntry *TDE = ActionVectorEntry.LinkedTrainEntryPtr;
18051  AnsiString FirstHeadCode = TDE->HeadCode;
18052  int LastRepeatNumber = TDE->NumberOfTrains - 1;
18053  // a shuttle has to have at least 1 repeat
18054  int IncrementalDigits = TDE->ActionVector.at(TDE->ActionVector.size() - 1).FrontStartOrRepeatDigits;
18055  TimeStr = GetRepeatHeadCode(36, FirstHeadCode, LastRepeatNumber, IncrementalDigits) + " at " +
18056  Utilities->Format96HHMM(GetRepeatTime(23, ActionVectorEntry.EventTime, y, IncMinutes));
18057  }
18058  }
18059  else if(ActionVectorEntry.SequenceType == IntermediateSequence)
18060  {
18061  if(ActionVectorEntry.FormatType == TimeTimeLoc)
18062  {
18063  // here need 2 entries if times different so push the first right away & the second later
18064  // if times same just give the arrival entry
18065  if(ActionVectorEntry.DepartureTime != ActionVectorEntry.ArrivalTime)
18066  {
18067  PartStr = "Arrives at " + ActionVectorEntry.LocationName;
18068  TimeStr = Utilities->Format96HHMM(GetRepeatTime(4, ActionVectorEntry.ArrivalTime, y, IncMinutes));
18069  OneTTEntry.Action = PartStr;
18070  OneTTEntry.Time = TimeStr;
18071  OneTTTrain.OneFormattedTrainVector.push_back(OneTTEntry);
18072  PartStr = "Departs from " + ActionVectorEntry.LocationName;
18073  TimeStr = Utilities->Format96HHMM(GetRepeatTime(5, ActionVectorEntry.DepartureTime, y, IncMinutes));
18074  }
18075  else
18076  {
18077  PartStr = "Arrives & departs " + ActionVectorEntry.LocationName;
18078  TimeStr = Utilities->Format96HHMM(GetRepeatTime(29, ActionVectorEntry.ArrivalTime, y, IncMinutes));
18079  }
18080  }
18081  else if((ActionVectorEntry.FormatType == TimeLoc) && (ActionVectorEntry.ArrivalTime != TDateTime(-1)))
18082  {
18083  PartStr = "Arrives at " + ActionVectorEntry.LocationName;
18084  TimeStr = Utilities->Format96HHMM(GetRepeatTime(6, ActionVectorEntry.ArrivalTime, y, IncMinutes));
18085  }
18086  else if((ActionVectorEntry.FormatType == TimeLoc) && (ActionVectorEntry.ArrivalTime == TDateTime(-1)))
18087  {
18088  PartStr = "Departs from " + ActionVectorEntry.LocationName;
18089  TimeStr = Utilities->Format96HHMM(GetRepeatTime(7, ActionVectorEntry.DepartureTime, y, IncMinutes));
18090  }
18091  else if(ActionVectorEntry.FormatType == PassTime)
18092  {
18093  PartStr = "Passes " + ActionVectorEntry.LocationName;
18094  TimeStr = Utilities->Format96HHMM(GetRepeatTime(8, ActionVectorEntry.EventTime, y, IncMinutes));
18095  }
18096  else if(ActionVectorEntry.Command == "jbo")
18097  {
18098  PartStr = "Joined at " + ActionVectorEntry.LocationName + " by";
18099  TimeStr = GetRepeatHeadCode(37, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
18100  Utilities->Format96HHMM(GetRepeatTime(9, ActionVectorEntry.EventTime, y, IncMinutes));
18101  }
18102  else if(ActionVectorEntry.Command == "fsp")
18103  {
18104  if(ActionVectorEntry.SplitDistribution != "") //new at v2.15.0
18105  {
18106  PartStr = "Splits from front [mass%-power% = " + ActionVectorEntry.SplitDistribution + "] at " + ActionVectorEntry.LocationName + " to form";
18107  }
18108  else
18109  {
18110  PartStr = "Splits from front [mass%-power% = 50-50] at " + ActionVectorEntry.LocationName + " to form";
18111  }
18112  TimeStr = GetRepeatHeadCode(38, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
18113  Utilities->Format96HHMM(GetRepeatTime(10, ActionVectorEntry.EventTime, y, IncMinutes));
18114  }
18115  else if(ActionVectorEntry.Command == "rsp")
18116  {
18117  if(ActionVectorEntry.SplitDistribution != "") //new at v2.15.0
18118  {
18119  PartStr = "Splits from front [mass%-power% = " + ActionVectorEntry.SplitDistribution + "] at " + ActionVectorEntry.LocationName + " to form";
18120  }
18121  else
18122  {
18123  PartStr = "Splits from front [mass%-power% = 50-50] at " + ActionVectorEntry.LocationName + " to form";
18124  }
18125  TimeStr = GetRepeatHeadCode(39, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
18126  Utilities->Format96HHMM(GetRepeatTime(11, ActionVectorEntry.EventTime, y, IncMinutes));
18127  }
18128  else if(ActionVectorEntry.Command == "cdt")
18129  {
18130  PartStr = "Changes direction at " + ActionVectorEntry.LocationName;
18131  TimeStr = Utilities->Format96HHMM(GetRepeatTime(12, ActionVectorEntry.EventTime, y, IncMinutes));
18132  }
18133  else if(ActionVectorEntry.Command == "dsc")
18134  {
18135  PartStr = "Changes description at " + ActionVectorEntry.LocationName;
18136  TimeStr = Utilities->Format96HHMM(GetRepeatTime(76, ActionVectorEntry.EventTime, y, IncMinutes));
18137  }
18138  else if(ActionVectorEntry.Command == "cms")
18139  {
18140  PartStr = "Change maximum speed to " + ActionVectorEntry.NewMaxSpeed + " at " + ActionVectorEntry.LocationName;
18141  TimeStr = Utilities->Format96HHMM(GetRepeatTime(78, ActionVectorEntry.EventTime, y, IncMinutes));
18142  }
18143  }
18144  else if(ActionVectorEntry.SequenceType == FinishSequence)
18145  {
18146  if(ActionVectorEntry.Command == "Fns")
18147  {
18148  PartStr = "At " + ActionVectorEntry.LocationName + " forms new service";
18149  TimeStr = GetRepeatHeadCode(40, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
18150  Utilities->Format96HHMM(GetRepeatTime(13, ActionVectorEntry.EventTime, y, IncMinutes));
18151  }
18152  else if(ActionVectorEntry.Command == "F-nshs")
18153  {
18154  PartStr = "At " + ActionVectorEntry.LocationName + " forms new service";
18155  TimeStr = ActionVectorEntry.NonRepeatingShuttleLinkHeadCode + " at " + Utilities->Format96HHMM
18156  (GetRepeatTime(17, ActionVectorEntry.EventTime, y, IncMinutes));
18157  }
18158  else if((ActionVectorEntry.Command == "Fns-sh") && (y < (TrainDataEntry.NumberOfTrains - 1)))
18159  {
18160  PartStr = "At " + ActionVectorEntry.LocationName + " forms new service ";
18161  TimeStr = GetRepeatHeadCode(41, ActionVectorEntry.OtherHeadCode, y + 1, IncDigits) + " at " +
18162  Utilities->Format96HHMM(GetRepeatTime(14, ActionVectorEntry.EventTime, y, IncMinutes));
18163  // y+1 because it's the NEXT service repeat number that is relevant
18164  }
18165  else if((ActionVectorEntry.Command == "Fns-sh") && (y >= (TrainDataEntry.NumberOfTrains - 1)))
18166  {
18167  PartStr = "At " + ActionVectorEntry.LocationName + " forms new service";
18168  TimeStr = ActionVectorEntry.NonRepeatingShuttleLinkHeadCode + " at " + Utilities->Format96HHMM
18169  (GetRepeatTime(15, ActionVectorEntry.EventTime, y, IncMinutes));
18170  }
18171  else if((ActionVectorEntry.Command == "Frh-sh") && (y < (TrainDataEntry.NumberOfTrains - 1)))
18172  {
18173  PartStr = "At " + ActionVectorEntry.LocationName + " forms new service";
18174  TimeStr = GetRepeatHeadCode(43, ActionVectorEntry.OtherHeadCode, y + 1, IncDigits) + " at " +
18175  Utilities->Format96HHMM(GetRepeatTime(16, ActionVectorEntry.EventTime, y, IncMinutes));
18176  // y+1 because it's the NEXT service repeat number that is relevant
18177  }
18178  else if((ActionVectorEntry.Command == "Frh-sh") && (y >= (TrainDataEntry.NumberOfTrains - 1)))
18179  {
18180  PartStr = "Terminates shuttle service at " + ActionVectorEntry.LocationName;
18181  // only used in chronological tt
18182  TimeStr = "End at " + Utilities->Format96HHMM(GetRepeatTime(28, ActionVectorEntry.EventTime, y, IncMinutes));
18183  // the "End at " is stripped out of the chronological tt but displayed in the traditional tt
18184  }
18185  else if(ActionVectorEntry.Command == "Frh")
18186  {
18187  PartStr = "Terminates at " + ActionVectorEntry.LocationName;
18188  // need here to examine the time of the preceding entry, may be ArrivalTime if TimeLoc, or EventTime otherwise
18189  if(z > 0)
18190  // should be for finish entry but include check for safety
18191  {
18192  if(ActionVector.at(z - 1).EventTime != TDateTime(-1))
18193  {
18194  TimeStr = Utilities->Format96HHMM(GetRepeatTime(30, ActionVector.at(z - 1).EventTime, y, IncMinutes));
18195  }
18196  else if(ActionVector.at(z - 1).ArrivalTime != TDateTime(-1))
18197  {
18198  TimeStr = Utilities->Format96HHMM(GetRepeatTime(31, ActionVector.at(z - 1).ArrivalTime, y, IncMinutes));
18199  }
18200  else
18201  {
18202  TimeStr = " "; // shouldn't ever get here
18203  }
18204  }
18205  }
18206  else if(ActionVectorEntry.Command == "Fer")
18207  {
18208  AnsiString AllowedExits;
18209  PartStr = "Exits railway" + GetExitLocationAndAt(0, ActionVectorEntry.ExitList, AllowedExits) + AllowedExits;
18210  TimeStr = Utilities->Format96HHMM(GetRepeatTime(18, ActionVectorEntry.EventTime, y, IncMinutes));
18211  }
18212  else if(ActionVectorEntry.Command == "Fjo")
18213  {
18214  PartStr = "At " + ActionVectorEntry.LocationName + " joins";
18215  TimeStr = GetRepeatHeadCode(44, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
18216  Utilities->Format96HHMM(GetRepeatTime(19, ActionVectorEntry.EventTime, y, IncMinutes));
18217  }
18218  }
18219  else if(ActionVectorEntry.SequenceType == SequTypeForRepeatEntry)
18220  {
18221  continue; // no entry needed for a repeat
18222  }
18223  OneTTEntry.Action = PartStr;
18224  OneTTEntry.Time = TimeStr;
18225  OneTTTrain.OneFormattedTrainVector.push_back(OneTTEntry);
18226  // one per action
18227  }
18228  OneTTLine.OneCompleteFormattedTrainVector.push_back(OneTTTrain);
18229  // one per repeat
18230  }
18231  AllTTTrains->push_back(OneTTLine); // one per repeating train
18232  }
18233  // AllTTTrains vector now complete
18234 
18235  std::ofstream TTFile(TTFileName.c_str()); //formatted timetable
18236 
18237  if(TTFile == 0)
18238  {
18239  StopTTClockMessage(64, "Formatted timetable file failed to open - can't be created");
18240  delete AllTTTrains;
18241  Utilities->CallLogPop(1567);
18242  return;
18243  }
18244 /* formatted timetable types
18245  class TOneTrainFormattedEntry
18246  {
18247  AnsiString Action;//includes location if relevant
18248  AnsiString Time;
18249  };
18250 
18251  typedef std::vector<TOneTrainFormattedEntry> TOneFormattedTrainVector;
18252 
18253  class TOneCompleteFormattedTrain//headcode + list of actions
18254  {
18255  public:
18256  AnsiString HeadCode;
18257  TOneFormattedTrainVector OneFormattedTrainVector;
18258  };
18259 
18260  typedef std::vector<TOneCompleteFormattedTrain> TOneCompleteFormattedTrainVector;//list af all repeats
18261 
18262  class TTrainFormattedInformation//contains all information for a single TT entry (including repeats)
18263  {
18264  public:
18265  AnsiString Header;//description, mass, power, brake rate etc
18266  int NumberOfTrains;// number of repeats + 1
18267  TOneCompleteFormattedTrainVector OneCompleteFormattedTrainVector;//list af all repeats
18268  };
18269 
18270  typedef std::vector<TTrainFormattedInformation> TAllFormattedTrains;//all timetable in formatted form
18271  //end of formatted timetable types
18272 */
18273 
18274  // new layout using multiple rows
18275  TTFile << TableTitle.c_str() << '\n' << '\n';
18276  for(unsigned int x = 0; x < AllTTTrains->size(); x++)
18277  {
18278  TTFile << AllTTTrains->at(x).Header.c_str();
18279  TTFile << '\n';
18280  TTFile << ','; // for the blank line above the action list
18281  for(int y = 0; y < AllTTTrains->at(x).NumberOfTrains; y++) // number of repeating trains
18282  {
18283  if(y < (AllTTTrains->at(x).NumberOfTrains - 1))
18284  {
18285  TTFile << AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).HeadCode.c_str() << ',';
18286  }
18287  else
18288  {
18289  TTFile << AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).HeadCode.c_str();
18290  }
18291  }
18292  TTFile << '\n' << '\n';
18293 
18294  for(unsigned int z = 0; z < AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(0).OneFormattedTrainVector.size(); z++)
18295  {
18296  TTFile << AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(0).OneFormattedTrainVector.at(z).Action.c_str() << ',';
18297  for(int y = 0; y < AllTTTrains->at(x).NumberOfTrains; y++) // number of repeating trains
18298  {
18299  if(y < (AllTTTrains->at(x).NumberOfTrains - 1))
18300  {
18301  TTFile << AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).OneFormattedTrainVector.at(z).Time.c_str() << ',';
18302  }
18303  else
18304  {
18305  TTFile << AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).OneFormattedTrainVector.at(z).Time.c_str();
18306  }
18307  }
18308  TTFile << '\n';
18309  }
18310  TTFile << '\n' << '\n';
18311  }
18312 
18313  TTFile.close();
18314 
18315  AnsiString TTFileName2 = TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss");
18316 
18317  TTFileName2 = CurDir + "\\Formatted timetables\\Timetable " + TTFileName2 + "; " + RailwayTitle + "; " + TimetableTitle + ".txt";
18318 
18319  std::ofstream TTFile2(TTFileName2.c_str()); //chronological timetable
18320 
18321  if(TTFile2 == 0)
18322  {
18323  StopTTClockMessage(67, "Chronological timetable file failed to open - can't be created");
18324  delete AllTTTrains;
18325  Utilities->CallLogPop(1710);
18326  return;
18327  }
18328  typedef std::multimap<AnsiString, AnsiString>TAnsiMultiMap;
18329  std::multimap<AnsiString, AnsiString>::iterator AMMIT;
18330  std::pair<AnsiString, AnsiString>AnsiMultiMapEntry;
18331 
18332  TAnsiMultiMap *TAMM = new TAnsiMultiMap;
18333  LastTTTime = ""; //records the very last time in the timetable - used in analysis file for Frh entries
18334 
18335  // multimap of AnsiStrings with TimeString as key (to sort automatically)
18336 
18337  TTFile2 << TableTitle.c_str() << '\n' << '\n';
18338  for(unsigned int x = 0; x < AllTTTrains->size(); x++)
18339  {
18340  for(int y = 0; y < AllTTTrains->at(x).NumberOfTrains; y++) // number of repeating trains
18341  {
18342  for(unsigned int z = 0; z < AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).OneFormattedTrainVector.size(); z++)
18343  {
18344  bool GiveMessagesFalse = false;
18345  AnsiString TimeString = AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).OneFormattedTrainVector.at(z).Time;
18346  AnsiString HeadCodeString = AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).HeadCode;
18347  AnsiString ActionString = AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).OneFormattedTrainVector.at(z).Action;
18348  if(CheckHeadCodeValidity(11, GiveMessagesFalse, TimeString.SubString(1, 4)))
18349  // 'NXNN at HH:MM' (will return true if H/C as integ check passed)
18350  {
18351  // fails for HH:MM because of ':' or 'End at HH:MM' because of ' '
18352  AnsiString OtherHeadCode = TimeString.SubString(1, 4);
18353  TimeString = TimeString.SubString(9, 5);
18354  ActionString += " " + OtherHeadCode;
18355  }
18356  if(TimeString.SubString(1, 7) == "End at ")
18357  // for Frh-sh final entry
18358  {
18359  TimeString = TimeString.SubString(8, 5);
18360  }
18361  AnsiString OneLine = TimeString + ' ' + HeadCodeString + ' ' + ActionString + '\n';
18362  AnsiMultiMapEntry.first = TimeString;
18363  AnsiMultiMapEntry.second = OneLine;
18364  TAMM->insert(AnsiMultiMapEntry);
18365  }
18366  }
18367  }
18368 
18369  for(AMMIT = TAMM->begin(); AMMIT != TAMM->end(); AMMIT++)
18370  {
18371  TTFile2 << (AMMIT->second).c_str();
18372  }
18373  delete AllTTTrains;
18374  delete TAMM;
18375  TTFile2.close();
18376  Utilities->CallLogPop(1580);
18377 }
18378 
18379 // ---------------------------------------------------------------------------
18380 
18381 bool TTrainController::CreateTTAnalysisFile(int Caller, AnsiString RailwayTitle, AnsiString TimetableTitle, AnsiString CurDir, bool ArrChecked, bool DepChecked,
18382  bool AtLocChecked, bool DirChecked, int ArrRange, int DepRange)
18383 {
18384 
18385  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CreateTTAnalysisFile");
18386  bool AnalysisError = false;
18387  AnsiString SequenceLog = "SequenceLog\n";
18388 
18389 /* Double crosslink (shuttle) table:
18390 
18391 Command Format OtherHead NonRepeating- LinkTrain- NonRepeating- Decsription
18392  Code ShuttleLink- EntryPtr ShuttleLink-
18393  HeadCode EntryPtr
18394 
18395 Snt-sh SNTShuttle Y (rtn shuttle) N Y (rtn sh) N Simple shuttle - no feeder service
18396 Frh-sh TimeCmdHeadCode Y (outwd shuttle) N Y (outwd sh) N Simple shuttle - no finishing service
18397 F-nshs FNSNonRepeatToShuttle N (shld be Y for outwd shuttle) Y (shld be N) Y (correct) N (correct) Feeder service link to shuttle
18398 Sns-sh SNSShuttle Y (rtn shuttle) Y (feeder) Y (rtn) Y (fdr) Shuttle link from feeder service
18399 Sns-fsh SNSNonRepeatFromShuttle N (shld be Y for rtn shuttle) Y (shld be N) Y (correct) N (correct) Finishing service link from shuttle
18400 Fns-sh FSHNewService Y (outwd shuttle) Y (finishing) Y (outwd sh) Y (finish) Shuttle link to finishing service
18401 
18402 Note: Any shuttle start can have any finish - feeder and finish, neither, feeder but no finish & vice versa.
18403 */
18404 
18405  try
18406  {
18407  //New section at v2.5.0 for tt conflict analysis
18408  /*
18409  typedef std::list<AnsiString> TServiceCallingLocsList;
18410  typedef std::map<AnsiString, TServiceCallingLocsList> TAllServiceCallingLocsMap;
18411 
18413  struct TLocServiceTimes
18414  {
18415  AnsiString Location;
18416  AnsiString ServiceAndRepeatNum;
18417  AnsiString AtLocTime;
18418  AnsiString ArrTime;
18419  AnsiString DepTime;
18420  AnsiString FrhMarker;
18421  };
18422  typedef std::vector<TLocServiceTimes> TLocServiceTimesVector;
18423  */
18424 
18425  //first have to check through all the services and give each one a unique name, or the analysis won't recognise differences between services that have the same reference
18426  //to do that need a new TrainDataVector as don't want to change anything in the original. TrainDataVectorCopy is used for building AllServiceCallingLocsMap & LocServiceTimesVector
18427 
18428 //create TrainDataVectorCopy and populate service refs with /1, /2 etc
18429  TrainDataVectorCopy = TrainDataVector; //don't need it on heap as TrainController is on the heap. Didn't need others in CreatFormattedTimetables but leave as is.
18430  TTrainDataVector::iterator TDVIt, TDVCopyIt;
18431  int Suffix = 0;
18432  int IteratorNumber = 0;
18433  AnsiString AnsiSuffix = "";
18434  for(TDVIt = TrainDataVector.begin(); TDVIt != TrainDataVector.end() - 1; TDVIt++)
18435  {
18436  IteratorNumber++; //first value in loop is 1
18437  Suffix = 0;
18438  for(TDVCopyIt = TrainDataVectorCopy.begin() + IteratorNumber; TDVCopyIt != TrainDataVectorCopy.end(); TDVCopyIt++)
18439  {
18440  if(TDVCopyIt->HeadCode == TDVIt->HeadCode)
18441  {
18442  Suffix++; //first value is 1
18443  AnsiSuffix = AnsiString(Suffix);
18444  TDVCopyIt->ServiceReference = TDVIt->HeadCode + "/" + AnsiSuffix; //set both the HeadCode + any forward slashes and numbers, this is because sometimes
18445  TDVCopyIt->HeadCode = TDVIt->HeadCode + "/" + AnsiSuffix; //service refs are used and sometimes H/Cs, so need them to be the same,
18446  } //they are all unique at this point anyway
18447  }
18448  }
18449 //now make all linked pointers in ActionVectorEntries point to links in the vector copy (still point to original vector at this stage)
18450 //and set the linked headcodes to the correct values - all unique at this point
18451  int Increment = 0, SlashPos;
18452  TActionVectorIterator AVEIt;
18453  AnsiString LinkedHeadCode;
18454 
18455  for(TDVCopyIt = TrainDataVectorCopy.begin(); TDVCopyIt != TrainDataVectorCopy.end(); TDVCopyIt++)
18456  {
18457  for(AVEIt = TDVCopyIt->ActionVector.begin(); AVEIt != TDVCopyIt->ActionVector.end(); AVEIt++)
18458  {
18459  if(AVEIt->LinkedTrainEntryPtr != NULL)
18460  {
18461  Increment = AVEIt->LinkedTrainEntryPtr - &TrainDataVector.at(0);
18462  AVEIt->LinkedTrainEntryPtr = &TrainDataVectorCopy.at(0) + Increment;
18463  //now set AVEIt->OtherHeadCode to the linked headcodes with /1, /2 etc
18464  //but note that these ARE HeadCodes and not service refs, so need to strip off any prefixes from the linked service refs
18465  LinkedHeadCode = (*AVEIt->LinkedTrainEntryPtr).ServiceReference;
18466  //now count from back until reach a '/' character or a non-integer character, if reach non-integer first then no '/' present at end of H/c (but may be one earlier as a prefix)
18467  SlashPos = 0;
18468  for(int x = LinkedHeadCode.Length(); x > 0; x--)
18469  {
18470  if(LinkedHeadCode[x] == '/')
18471  {
18472  SlashPos = LinkedHeadCode.Length() - x + 1;
18473  break;
18474  }
18475  else if((LinkedHeadCode[x] != '0') && (LinkedHeadCode[x] != '1') && (LinkedHeadCode[x] != '2') && (LinkedHeadCode[x] != '3') &&
18476  (LinkedHeadCode[x] != '4') && (LinkedHeadCode[x] != '5') && (LinkedHeadCode[x] != '6') && (LinkedHeadCode[x] != '7') &&
18477  (LinkedHeadCode[x] != '8') && (LinkedHeadCode[x] != '8'))
18478  {
18479  break;
18480  }
18481  }
18482  //now strip off any prefix
18483  AVEIt->OtherHeadCode = LinkedHeadCode.SubString(LinkedHeadCode.Length() - 3 - SlashPos, 4 + SlashPos);
18484  }
18485  else
18486  {
18487  AVEIt->OtherHeadCode = "";
18488  }
18489  if(AVEIt->NonRepeatingShuttleLinkEntryPtr != NULL)
18490  {
18491  Increment = AVEIt->NonRepeatingShuttleLinkEntryPtr - &TrainDataVector.at(0);
18492  AVEIt->NonRepeatingShuttleLinkEntryPtr = &TrainDataVectorCopy.at(0) + Increment;
18493  //now set AVEIt->NonRepeatingShuttleLinkHeadCode to the linked headcodes with /1, /2 etc
18494  //but note that these ARE HeadCodes and not service refs, so need to strip off any prefixes from the linked service refs
18495  LinkedHeadCode = (*AVEIt->NonRepeatingShuttleLinkEntryPtr).ServiceReference;
18496  //now count from back until reach a '/' character or a non-integer character, if reach non-integer first then no '/' present at end of H/c (but may be one earlier as a prefix)
18497  SlashPos = 0;
18498  for(int x = LinkedHeadCode.Length(); x > 0; x--)
18499  {
18500  if(LinkedHeadCode[x] == '/')
18501  {
18502  SlashPos = LinkedHeadCode.Length() - x + 1;
18503  break;
18504  }
18505  else if((LinkedHeadCode[x] != '0') && (LinkedHeadCode[x] != '1') && (LinkedHeadCode[x] != '2') && (LinkedHeadCode[x] != '3') &&
18506  (LinkedHeadCode[x] != '4') && (LinkedHeadCode[x] != '5') && (LinkedHeadCode[x] != '6') && (LinkedHeadCode[x] != '7') &&
18507  (LinkedHeadCode[x] != '8') && (LinkedHeadCode[x] != '8'))
18508  {
18509  break;
18510  }
18511  }
18512  //now strip off any prefix
18513  AVEIt->NonRepeatingShuttleLinkHeadCode = LinkedHeadCode.SubString(LinkedHeadCode.Length() - 3 - SlashPos, 4 + SlashPos);
18514  }
18515  else
18516  {
18517  AVEIt->NonRepeatingShuttleLinkHeadCode = "";
18518  }
18519  }
18520  }
18521  //from here only TrainDataVectorCopy used
18522  SequenceLog += "1\n";
18523  //build AllServiceCallingLocsMap, it only uses the base service reference (with /1, /2 etc suffixes) as later times are calculated from the repeat number
18524  TServiceCallingLocsList ServiceCallingLocsList;
18525  std::pair<AnsiString, TServiceCallingLocsList> AllServiceCallingLocsEntry;
18526  for(unsigned int x = 0; x < TrainDataVectorCopy.size(); x++)
18527  {
18528  const TTrainDataEntry &TrainDataEntry = TrainDataVectorCopy.at(x);
18529  const TActionVector &ActionVector = TrainDataEntry.ActionVector;
18530  AllServiceCallingLocsEntry.first = TrainDataEntry.ServiceReference;
18531  ServiceCallingLocsList.clear();
18532  if(ActionVector.empty())
18533  {
18534  continue;
18535  }
18536  if(ActionVector.at(0).SignallerControl)
18537  {
18538  continue;
18539  }
18540  for(unsigned int z = 0; z < ActionVector.size(); z++)
18541  {
18542  TActionVectorEntry AVE = ActionVector.at(z);
18543  if(AVE.FormatType == StartNew)
18544  {
18545  if(AVE.LocationType == AtLocation) //located Snt
18546  {
18547  ServiceCallingLocsList.push_back(AVE.LocationName);
18548  }
18549  else //unlocated Snt (could be entering at continuation)
18550  {
18552  if(TE.ActiveTrackElementName != "")
18553  {
18554  ServiceCallingLocsList.push_back(TE.ActiveTrackElementName);
18555  }
18556  else
18557  {
18558  int HLoc = TE.HLoc;
18559  int VLoc = TE.VLoc;
18560  AnsiString HString;
18561  AnsiString VString;
18562  if(HLoc < 0)
18563  {
18564  HString = AnsiString('N') + AnsiString(HLoc).SubString(2, AnsiString(HLoc).Length() - 1); //strip off '-'
18565  }
18566  else
18567  {
18568  HString = AnsiString(HLoc);
18569  }
18570  if(VLoc < 0)
18571  {
18572  VString = AnsiString('N') + AnsiString(VLoc).SubString(2, AnsiString(VLoc).Length() - 1); //strip off '-'
18573  }
18574  else
18575  {
18576  VString = AnsiString(VLoc);
18577  }
18578  ServiceCallingLocsList.push_back(HString + '-' + VString);
18579  }
18580  }
18581  }
18582  else if(AVE.SequenceType == StartSequence) //other start entries, all located
18583  {
18584  ServiceCallingLocsList.push_back(AVE.LocationName);
18585  }
18586  else if(AVE.FormatType == TimeLoc) //z must be > 0
18587  {
18588  if(ServiceCallingLocsList.back() != AVE.LocationName)
18589  {
18590  ServiceCallingLocsList.push_back(AVE.LocationName); //may be listed twice in succession so only want one entry
18591  }
18592  }
18593  else if(AVE.FormatType == PassTime)
18594  {
18595  ServiceCallingLocsList.push_back(AVE.LocationName);
18596  }
18597  else if(AVE.FormatType == TimeTimeLoc)
18598  {
18599  ServiceCallingLocsList.push_back(AVE.LocationName);
18600  }
18601  else if(AVE.Command == "cdt") //list if not next to start or finish
18602  {
18603  if(ActionVector.at(z-1).SequenceType == StartSequence)
18604  {
18605  continue;
18606  }
18607  else if(ActionVector.at(z+1).SequenceType == FinishSequence) //although deal with Fer entries cdt (train stopped) can't precede FER (train moving)
18608  {
18609  continue;
18610  }
18611  else
18612  {
18613  AnsiString TimeString = Utilities->Format96HHMM(AVE.EventTime);
18614  ServiceCallingLocsList.push_back("%%%" + TimeString); //%%% is a marker - unlikely that any locations will begin with this & easy to check to identify a time
18615  }
18616  }
18617  else if(AVE.FormatType == ExitRailway) //Fer
18618  {
18619  TTrackElement TE = Track->TrackElementAt(995, AVE.ExitList.front());
18620  AnsiString LName = TE.ActiveTrackElementName;
18621  if(LName != "")
18622  {
18623  ServiceCallingLocsList.push_back(LName);
18624  }
18625  else
18626  {
18627  int HLoc = TE.HLoc;
18628  int VLoc = TE.VLoc;
18629  AnsiString HString;
18630  AnsiString VString;
18631  if(HLoc < 0)
18632  {
18633  HString = AnsiString('N') + AnsiString(HLoc).SubString(2, AnsiString(HLoc).Length() - 1); //strip off '-'
18634  }
18635  else
18636  {
18637  HString = AnsiString(HLoc);
18638  }
18639  if(VLoc < 0)
18640  {
18641  VString = AnsiString('N') + AnsiString(VLoc).SubString(2, AnsiString(VLoc).Length() - 1); //strip off '-'
18642  }
18643  else
18644  {
18645  VString = AnsiString(VLoc);
18646  }
18647  ServiceCallingLocsList.push_back(HString + '-' + VString);
18648  }
18649  }
18650  }
18651  AllServiceCallingLocsEntry.second = ServiceCallingLocsList;
18652  AllServiceCallingLocsMap.insert(AllServiceCallingLocsEntry);
18653  }
18654  //AllServiceCallingLocsMap built
18655  SequenceLog += "2\n";
18656 /*
18657 // this sequence is to test the validity of AllServiceCallingLocsMap
18658  AnsiString TestFile = CurDir + "\\Formatted timetables\\TestFile; " + RailwayTitle + "; " + TimetableTitle + ".txt";
18659  std::ofstream Test(TestFile.c_str());
18660 
18661  if(TestFile == 0)
18662  {
18663  ShowMessage("TestFile failed to open - can't be created");
18664  Utilities->CallLogPop();
18665  return false;
18666  }
18667 
18668  for(TAllServiceCallingLocsMap::iterator ASCLIt = AllServiceCallingLocsMap.begin(); ASCLIt != AllServiceCallingLocsMap.end(); ASCLIt++)
18669  {
18670  Test << ASCLIt->first << '\n'; //service ref
18671  for(TServiceCallingLocsList::iterator SCLIt = ASCLIt->second.begin(); SCLIt != ASCLIt->second.end(); SCLIt++)
18672  {
18673  Test << *SCLIt << '\n';
18674  }
18675  Test << "\n\n";
18676  }
18677  Test.close();
18678  Utilities->CallLogPop();
18679  return true;
18680 */
18681  //initialise variables before calc LastTTTime & build LocServiceTimesVector
18682  if(TrainDataVectorCopy.empty())
18683  {
18684  ShowMessage("Unable to create a program-readable timetable - please check the timetable file validity");
18685  Utilities->CallLogPop(2209);
18686  return(false);
18687  }
18688  TLocServiceTimes TLSTEntry;
18689  TLocServiceTimesVector LocServiceTimesVector; //will be on heap as TrainController is on the heap
18690  bool NumPlatsAtThisLocCalculated = false, ArrivalsPrinted = false, DeparturesPrinted = false, AtLocsPrinted = false;
18691  AnsiString PreviousService = "", PreviousServiceAndRepeatNumTotalOutput = "", BasicTime = "", MinuteString = "", LastAnsiTime = "";
18692  int NumTrains = 0, NumPlats = 0, LastFrhCount = 0, FrhCount = 0, NumTrainsAtLoc = 0;
18693  LastTTTime = "";
18694  SequenceLog += "3\n";
18695  //calculate LastTTTime
18696  for(unsigned int x = 0; x < TrainDataVectorCopy.size(); x++)
18697  {
18698  TTrainDataEntry &TrainDataEntry = TrainDataVectorCopy.at(x);
18699  TActionVector &ActionVector = TrainDataEntry.ActionVector;
18700  TActionVectorIterator AVLast = ActionVector.end() - 1; //points to last entry
18701  TDateTime LastTDTime;
18702  int IncMinutes = 0;
18703  NumTrains = TrainDataEntry.NumberOfTrains;
18704  if(ActionVector.empty())
18705  {
18706  continue;
18707  }
18708  if(ActionVector.at(0).SignallerControl)
18709  {
18710  continue;
18711  }
18712  if(AVLast->FormatType == Repeat)
18713  {
18714  IncMinutes = ActionVector.at(ActionVector.size() - 1).RearStartOrRepeatMins;
18715  AVLast--; //now points to the command before the repeat
18716  }
18717  if(AVLast->FormatType == FinRemHere) //not 'else if' as may have both a repeat and an Frh
18718  {
18719  AVLast--; //points to last timed entry
18720  }
18721  //here AVLast points to last entry with a time
18722  if(AVLast->ArrivalTime != TDateTime(-1))
18723  {
18724  LastTDTime = AVLast->ArrivalTime;
18725  }
18726  else if(AVLast->EventTime != TDateTime(-1)) //can't be a departure time
18727  {
18728  LastTDTime = AVLast->EventTime;
18729  }
18730  else
18731  {
18732  continue; //shouldn't ever reach here but if do then skip this service
18733  }
18734  if(NumTrains == 1)
18735  {
18736  LastAnsiTime = Utilities->Format96HHMM(LastTDTime);
18737  }
18738  else
18739  {
18740  LastAnsiTime = Utilities->Format96HHMM(GetRepeatTime(59, LastTDTime, NumTrains - 1, IncMinutes));
18741  }
18742  if(LastAnsiTime > LastTTTime)
18743  {
18744  LastTTTime = LastAnsiTime;
18745  }
18746  }
18747  SequenceLog += "4\n";
18748 //build LocServiceTimesVector
18749 
18750 /*
18751  struct TLocServiceTimes
18752  {
18753  AnsiString Location;
18754  AnsiString ServiceAndRepeatNum;
18755  AnsiString AtLocTime;
18756  AnsiString ArrTime;
18757  AnsiString DepTime;
18758  AnsiString FrhMarker;
18759  };
18760  typedef std::vector<TLocServiceTimes> TLocServiceTimesVector;
18761 
18762 This works as follows:
18763 ServiceAndRepeatNum is taken from the TrainDataVectorCopy as it is the same for all actionvector entries
18764 Location is taken from ActionVectorEntry.LocationName if there is one, or from the H & V locations if not (e.g. at an unnamed Fer)
18765 AtLocTime is always entered either on its own or with ArrTime or DepTime as appropriate
18766 
18767 Every action for every train is examined and times entered as follows:-
18768 a) a located Snt: entry time becomes the AtLocTime, and all subsequent minutes entered too up to but not including a departure or a finish
18769 b) an unlocated Snt: entry time becomes DepTime
18770 c) all other start entries: entry time becomes AtLoc, and all subsequent minutes entered too up to but not including a departure or a finish
18771 d) TimeLoc Arr: entry time becomes ArrTime, and all subsequent minutes entered too up to but not including a departure or a finish
18772 e) TimeLoc Dep: entry time becomes DepTime, checks if DepTime same as earlier ArrTime and if so all times go in as one entry
18773 f) TimeTimeLoc: Arrival time entered as ArrTime, a check if Arr & Dep same and if so go in as one entry, else all minutes between entered as AtLocs then DepTime
18774 g) ExitRailway (Fer): check if located and use LocationName if so. else use H & V positions, time becomes AtLocTime
18775 h) Frh: use the earlier vector time as the AtLocTime and set FrhMarker, and enter all minutes to end of timetable as AtLocs
18776 i) Frh-sh: for the last train use time as AtLocTime, set FrhMarker, and enter all minutes to end of timetable as AtLocs
18777 j) all other finish entries (all link to another service) are ignored as will be listed for the linked service
18778 */
18779  for(unsigned int x = 0; x < TrainDataVectorCopy.size(); x++)
18780  {
18781  const TTrainDataEntry &TrainDataEntry = TrainDataVectorCopy.at(x);
18782  const TActionVector &ActionVector = TrainDataEntry.ActionVector;
18783  AnsiString ServiceRef = TrainDataEntry.ServiceReference;
18784  int IncMinutes = 0;
18785  NumTrains = TrainDataEntry.NumberOfTrains;
18786  if(ActionVector.empty())
18787  {
18788  continue;
18789  }
18790  if(ActionVector.at(0).SignallerControl)
18791  {
18792  continue;
18793  }
18794  if(ActionVector.at(ActionVector.size() - 1).FormatType == Repeat)
18795  {
18796  IncMinutes = ActionVector.at(ActionVector.size() - 1).RearStartOrRepeatMins;
18797  }
18798  for(int y = 0; y < NumTrains; y++) //y is the repeat number
18799  {
18800  if(NumTrains == 1)
18801  {
18802  TLSTEntry.ServiceAndRepeatNum = ServiceRef;
18803  }
18804  else if(y == 0)
18805  {
18806  TLSTEntry.ServiceAndRepeatNum = ServiceRef + " (First service)";
18807  }
18808  else
18809  {
18810  TLSTEntry.ServiceAndRepeatNum = ServiceRef + " (Repeat " + AnsiString(y) + ")";
18811  }
18812  for(unsigned int z = 0; z < ActionVector.size(); z++)
18813  {
18814  TActionVectorEntry AVE = ActionVector.at(z);
18815  TLSTEntry.AtLocTime = "";
18816  TLSTEntry.ArrTime = "";
18817  TLSTEntry.DepTime = "";
18818  TLSTEntry.Location = "";
18819  TLSTEntry.FrhMarker = "";
18820 
18821  if(AVE.FormatType == StartNew) //Snt only
18822  {
18823  if(AVE.LocationType == AtLocation) //located Snt, class time as AtLocTime
18824  {
18825  TLSTEntry.Location = AVE.LocationName;
18826  TLSTEntry.AtLocTime = Utilities->Format96HHMM(GetRepeatTime(58, AVE.EventTime, y, IncMinutes));
18827  LocServiceTimesVector.push_back(TLSTEntry);
18828 
18829  //now look forwards until find a departure or a Fns, Fns-sh etc & add in all the minutes up to but not including the dep or finish times
18830  AnsiString IncTime = "", FoundStopTime = ""; //these handled in later checks
18831  for(unsigned int a = z + 1; a < ActionVector.size(); a++)
18832  {
18833  if(ActionVector.at(a).FormatType == TimeLoc) //must be a departure
18834  {
18835  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(62, ActionVector.at(a).DepartureTime, y, IncMinutes));
18836  break;
18837  }
18838  if(ActionVector.at(a).SequenceType == FinishSequence) //finish catered in a later test
18839  {
18840  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(63, ActionVector.at(a).EventTime, y, IncMinutes));
18841  break;
18842  }
18843  }
18844  if(FoundStopTime == "")
18845  {
18846  throw Exception("Failure to determine FoundStopTime for located Snt");
18847  }
18848  int WhileCount = 0;
18849  while(true)
18850  {
18851  //add minutes until reach FoundStopTime but don't add that time
18852  WhileCount++;
18853  IncTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
18854  TLSTEntry.AtLocTime = IncTime; //all entered times will be AtLocs
18855  TLSTEntry.DepTime = "";
18856  TLSTEntry.ArrTime = "";
18857  if(IncTime >= FoundStopTime) //don't add that time
18858  {
18859  break;
18860  }
18861  LocServiceTimesVector.push_back(TLSTEntry);
18862  if(WhileCount > 2000)
18863  {
18864  throw Exception("While loop failed to break in 2000 loops for located Snt");
18865  }
18866  }
18867  }
18868  else //unlocated Snt, use the EventTime as DepTime for this vector
18869  {
18871  if(TE.ActiveTrackElementName != "")
18872  {
18873  TLSTEntry.Location = TE.ActiveTrackElementName;
18874  }
18875  else
18876  {
18877  int HLoc = TE.HLoc;
18878  int VLoc = TE.VLoc;
18879  AnsiString HString;
18880  AnsiString VString;
18881  if(HLoc < 0)
18882  {
18883  HString = AnsiString('N') + AnsiString(HLoc).SubString(2, AnsiString(HLoc).Length() - 1); //strip off '-'
18884  }
18885  else
18886  {
18887  HString = AnsiString(HLoc);
18888  }
18889  if(VLoc < 0)
18890  {
18891  VString = AnsiString('N') + AnsiString(VLoc).SubString(2, AnsiString(VLoc).Length() - 1); //strip off '-'
18892  }
18893  else
18894  {
18895  VString = AnsiString(VLoc);
18896  }
18897  TLSTEntry.Location = HString + '-' + VString;
18898  }
18899  TLSTEntry.DepTime = Utilities->Format96HHMM(GetRepeatTime(49, AVE.EventTime, y, IncMinutes));
18900  TLSTEntry.AtLocTime = TLSTEntry.DepTime;
18901  LocServiceTimesVector.push_back(TLSTEntry);
18902  }
18903  }
18904 
18905  else if(AVE.SequenceType == StartSequence) //other start entries, all located
18906  {
18907  TLSTEntry.Location = AVE.LocationName;
18908  TLSTEntry.AtLocTime = Utilities->Format96HHMM(GetRepeatTime(50, AVE.EventTime, y, IncMinutes));
18909  LocServiceTimesVector.push_back(TLSTEntry);
18910  //now look forwards until find a departure or a Fns, Fns-sh etc & add in all the minutes up to but not including the dep or finish times
18911  AnsiString IncTime = "", FoundStopTime = ""; //these handled in other checks
18912  for(unsigned int a = z + 1; a < ActionVector.size(); a++)
18913  {
18914  if(ActionVector.at(a).FormatType == TimeLoc) //must be a departure
18915  {
18916  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(64, ActionVector.at(a).DepartureTime, y, IncMinutes));
18917  break;
18918  }
18919  if(ActionVector.at(a).SequenceType == FinishSequence) //finish catered in a later test
18920  {
18921  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(65, ActionVector.at(a).EventTime, y, IncMinutes));
18922  break;
18923  }
18924  }
18925  if(FoundStopTime == "")
18926  {
18927  throw Exception("Failure to determine FoundStopTime for SequenceType == StartSequence");
18928  }
18929  int WhileCount = 0;
18930  while(true)
18931  {
18932  //add minutes until reach FoundStopTime but don't add that time
18933  WhileCount++;
18934  IncTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
18935  TLSTEntry.AtLocTime = IncTime; //all entered times will be AtLocs
18936  TLSTEntry.DepTime = "";
18937  TLSTEntry.ArrTime = "";
18938  if(IncTime >= FoundStopTime) //don't add that time
18939  {
18940  break;
18941  }
18942  LocServiceTimesVector.push_back(TLSTEntry);
18943  if(WhileCount > 2000)
18944  {
18945  throw Exception("While loop failed to break in 2000 loops for SequenceType == StartSequence");
18946  }
18947  }
18948  }
18949 
18950  else if(AVE.FormatType == TimeLoc) //could be arr or dep, if arrival add in all mins to the departure or finish
18951  {
18952  TLSTEntry.Location = AVE.LocationName;
18953  if(AVE.ArrivalTime > TDateTime(-1)) //one or other set, not both, in this case arrival
18954  {
18955  bool SkipAddingMinutes = false;
18956  TLSTEntry.ArrTime = Utilities->Format96HHMM(GetRepeatTime(51, AVE.ArrivalTime, y, IncMinutes));
18957  TLSTEntry.AtLocTime = TLSTEntry.ArrTime;
18958  LocServiceTimesVector.push_back(TLSTEntry); //Arr and AtLoc added (may be popped if dep time found to be same at next TimeLoc)
18959  //now look forwards until find a departure or a Fns, Fns-sh etc & add in all the minutes up to but not including the dep or finish times
18960  AnsiString IncTime = "", FoundStopTime = ""; //these handled in other checks
18961  for(unsigned int a = z + 1; a < ActionVector.size(); a++)
18962  {
18963  if(ActionVector.at(a).FormatType == TimeLoc) //must be a departure
18964  {
18965  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(66, ActionVector.at(a).DepartureTime, y, IncMinutes));
18966  break;
18967  }
18968  if(ActionVector.at(a).SequenceType == FinishSequence) //finish catered for in a later test
18969  {
18970  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(67, ActionVector.at(a).EventTime, y, IncMinutes));
18971  if((a <= (z + 2)) && (FoundStopTime == TLSTEntry.ArrTime) && ((ActionVector.at(a).LinkedTrainEntryPtr != NULL) || (ActionVector.at(a).NonRepeatingShuttleLinkEntryPtr != NULL)))
18972  //finish immediately after arrival at same time, and a forward linked service. Added at v2.6.0 to prevent two linked trains being listed at same location
18973  //at v2.10.0 changed (a == z + 1) to (a <= (z + 2)) as can have a cdt between, this allows for that
18974  {
18975  LocServiceTimesVector.pop_back(); //pop the entry as the linked train will be listed at the relevant time and don't want to list both
18976  SkipAddingMinutes = true;
18977  }
18978  break;
18979  }
18980  }
18981  if(FoundStopTime == "")
18982  {
18983  throw Exception("Failure to determine FoundStopTime for SequenceType == StartSequence");
18984  }
18985  if(!SkipAddingMinutes)
18986  {
18987  int WhileCount = 0;
18988  while(true)
18989  {
18990  //add minutes until reach FoundStopTime but don't add that time
18991  WhileCount++;
18992  IncTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
18993  TLSTEntry.AtLocTime = IncTime; //all entered times will be AtLocs
18994  TLSTEntry.DepTime = "";
18995  TLSTEntry.ArrTime = "";
18996  if(IncTime >= FoundStopTime) //don't add that time
18997  {
18998  break;
18999  }
19000  LocServiceTimesVector.push_back(TLSTEntry);
19001  if(WhileCount > 2000)
19002  {
19003  throw Exception("While loop failed to break in 2000 loops for SequenceType == StartSequence");
19004  }
19005  }
19006  }
19007  }
19008  else if(AVE.DepartureTime > TDateTime(-1)) //need to check if the arrival time (which should already be listed) is same and if so put all times on one line
19009  {
19010  TLSTEntry.DepTime = Utilities->Format96HHMM(GetRepeatTime(52, AVE.DepartureTime, y, IncMinutes));
19011  TLSTEntry.AtLocTime = TLSTEntry.DepTime;
19012  if((TLSTEntry.Location == LocServiceTimesVector.back().Location) && (TLSTEntry.ServiceAndRepeatNum == LocServiceTimesVector.back().ServiceAndRepeatNum)) //if not it's a new service
19013  {
19014  if(TLSTEntry.DepTime == LocServiceTimesVector.back().ArrTime)
19015  {
19016  TLSTEntry.ArrTime = LocServiceTimesVector.back().ArrTime;
19017  LocServiceTimesVector.pop_back();
19018  LocServiceTimesVector.push_back(TLSTEntry); //Arr, Dep and AtLoc added in place of earlier Arr entry.
19019  }
19020  else //just add the dep & atloc times
19021  {
19022  TLSTEntry.ArrTime = "";
19023  LocServiceTimesVector.push_back(TLSTEntry);
19024  }
19025  }
19026  else //just add the dep & atloc times
19027  {
19028  TLSTEntry.ArrTime = "";
19029  LocServiceTimesVector.push_back(TLSTEntry);
19030  }
19031  }
19032  }
19033 
19034  else if(AVE.FormatType == TimeTimeLoc)
19035  {
19036  TLSTEntry.Location = AVE.LocationName;
19037  if(AVE.ArrivalTime > TDateTime(-1)) //should be
19038  {
19039  TLSTEntry.ArrTime = Utilities->Format96HHMM(GetRepeatTime(53, AVE.ArrivalTime, y, IncMinutes));
19040  TLSTEntry.AtLocTime = TLSTEntry.ArrTime;
19041  }
19042  if(AVE.DepartureTime > TDateTime(-1)) //should be
19043  {
19044  TLSTEntry.DepTime = Utilities->Format96HHMM(GetRepeatTime(54, AVE.DepartureTime, y, IncMinutes));
19045  }
19046  if(TLSTEntry.ArrTime == TLSTEntry.DepTime)
19047  {
19048  LocServiceTimesVector.push_back(TLSTEntry);
19049  }
19050  else
19051  {
19052  AnsiString TempDepTime = TLSTEntry.DepTime; //save it temporarily
19053  TLSTEntry.DepTime = "";
19054  LocServiceTimesVector.push_back(TLSTEntry); //push just the arrival and AtLoc times
19055  TLSTEntry.ArrTime = ""; //done with this now
19056  while(TLSTEntry.AtLocTime < TempDepTime)
19057  {
19058  TLSTEntry.AtLocTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
19059  if(TLSTEntry.AtLocTime == TempDepTime)
19060  {
19061  TLSTEntry.DepTime = TempDepTime; //restore value
19062  LocServiceTimesVector.push_back(TLSTEntry); //push the AtLoc and Dep times - will finish loop after this
19063  }
19064  else
19065  {
19066  LocServiceTimesVector.push_back(TLSTEntry); //push the AtLoc time on its own
19067  }
19068  }
19069  }
19070  }
19071 
19072  else if(AVE.FormatType == PassTime) //added at v2.9.1
19073  { //adds 2 entries, 1st with PassTime as ArrTime and AtLocTime, 2nd with PassTime as AtLocTime & DepTime
19074  TLSTEntry.Location = AVE.LocationName;;
19075  TLSTEntry.AtLocTime = Utilities->Format96HHMM(GetRepeatTime(73, AVE.EventTime, y, IncMinutes));
19076  TLSTEntry.ArrTime = TLSTEntry.AtLocTime; //DepTime already set to null
19077  LocServiceTimesVector.push_back(TLSTEntry); //1st entry
19078  TLSTEntry.ArrTime = ""; //need to reset this to null
19079  TLSTEntry.DepTime = TLSTEntry.AtLocTime;
19080  LocServiceTimesVector.push_back(TLSTEntry); //2nd entry
19081  }
19082 
19083  else if(AVE.FormatType == ExitRailway) //Fer
19084  {
19085  TLSTEntry.ArrTime = Utilities->Format96HHMM(GetRepeatTime(77, AVE.EventTime, y, IncMinutes)); //need this as arrival time so arrival analysis works
19086  //properly for exits (continuation entries use dep time so ok)
19087  //added at v2.20.3
19088  TLSTEntry.AtLocTime = Utilities->Format96HHMM(GetRepeatTime(55, AVE.EventTime, y, IncMinutes));
19089  //don't know which exit will be used during operation so use the first in ExitList, if several with different names then will
19090  //be wrong, but can't guess from here & most will have same name
19091  AnsiString LName = Track->TrackElementAt(990, AVE.ExitList.front()).ActiveTrackElementName;
19092  if(LName != "")
19093  {
19094  TLSTEntry.Location = LName;
19095  }
19096  else
19097  {
19098  int HLoc = Track->TrackElementAt(991, AVE.ExitList.front()).HLoc;
19099  int VLoc = Track->TrackElementAt(992, AVE.ExitList.front()).VLoc;
19100  AnsiString HString;
19101  AnsiString VString;
19102  if(HLoc < 0)
19103  {
19104  HString = AnsiString('N') + AnsiString(HLoc).SubString(2, AnsiString(HLoc).Length() - 1); //strip off '-'
19105  }
19106  else
19107  {
19108  HString = AnsiString(HLoc);
19109  }
19110  if(VLoc < 0)
19111  {
19112  VString = AnsiString('N') + AnsiString(VLoc).SubString(2, AnsiString(VLoc).Length() - 1); //strip off '-'
19113  }
19114  else
19115  {
19116  VString = AnsiString(VLoc);
19117  }
19118  TLSTEntry.Location = HString + '-' + VString;
19119  }
19120  LocServiceTimesVector.push_back(TLSTEntry); //just use the exit time as AtLocTime
19121  }
19122 
19123  else if(AVE.FormatType == FinRemHere) //Frh, not Frh-sh, that dealt with next
19124  {
19125  AnsiString FrhTime;
19126  if(ActionVector.at(z - 1).ArrivalTime != TDateTime(-1))
19127  {
19128  FrhTime = Utilities->Format96HHMM(GetRepeatTime(56, ActionVector.at(z - 1).ArrivalTime, y, IncMinutes));
19129  }
19130  else if(ActionVector.at(z - 1).EventTime != TDateTime(-1))
19131  {
19132  FrhTime = Utilities->Format96HHMM(GetRepeatTime(57, ActionVector.at(z - 1).EventTime, y, IncMinutes));
19133  }
19134  TLSTEntry.AtLocTime = FrhTime; //use the last entry time as the first recorded time
19135  TLSTEntry.Location = AVE.LocationName;
19136  AnsiString IncTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
19137  TLSTEntry.FrhMarker = "Frh";
19138  LocServiceTimesVector.push_back(TLSTEntry);
19139  TLSTEntry.FrhMarker = "";
19140  //add all times from next minute to end of timetable
19141  while(IncTime <= LastTTTime)
19142  {
19143  TLSTEntry.AtLocTime = IncTime;
19144  LocServiceTimesVector.push_back(TLSTEntry);
19145  IncTime = Utilities->IncrementAnsiTimeOneMinute(IncTime);
19146  }
19147  }
19148 
19149  else if(AVE.Command == "Frh-sh") //do nothing if links to other shuttle but treat as Frh when remaining here
19150  {
19151  if(y == NumTrains - 1) //last repeat, it remains here when accessed for the last train
19152  {
19153  TLSTEntry.AtLocTime = Utilities->Format96HHMM(GetRepeatTime(68, AVE.EventTime, y, IncMinutes));
19154  TLSTEntry.Location = AVE.LocationName;
19155  AnsiString IncTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
19156  TLSTEntry.FrhMarker = "Frh";
19157  LocServiceTimesVector.push_back(TLSTEntry);
19158  TLSTEntry.FrhMarker = "";
19159  //add all times from next minute to end of timetable
19160  while(IncTime <= LastTTTime)
19161  {
19162  TLSTEntry.AtLocTime = IncTime;
19163  LocServiceTimesVector.push_back(TLSTEntry);
19164  IncTime = Utilities->IncrementAnsiTimeOneMinute(IncTime);
19165  }
19166  }
19167  }
19168 
19169  else if(AVE.SequenceType == FinishSequence) //other finish types - all located & all link to another service
19170  {
19171  //nothing is done here as the entry will be listed at this time under the new service reference
19172  }
19173  }
19174  }
19175  }
19176  SequenceLog += "5\n";
19177  //now sort in location order
19178  std::sort(LocServiceTimesVector.begin(), LocServiceTimesVector.end(), &LocServiceTimesLocationSort); //LocServiceTimesLocationSort is a function pointer
19179  //LocServiceTimesVector now complete & sorted in location order
19180 
19181 /*
19182 //start of debugging section
19183 //create LocServiceTimesVector output file for debugging purposes
19184  AnsiString LSTVTestFile = CurDir + "\\Formatted timetables\\LSTVTestFile; " + RailwayTitle + "; " + TimetableTitle + ".txt";
19185  std::ofstream LSTVFile(LSTVTestFile.c_str());
19186  for(TLocServiceTimesVector::iterator LSTVIt = LocServiceTimesVector.begin(); LSTVIt != LocServiceTimesVector.end(); LSTVIt++)
19187  {
19188  LSTVFile << LSTVIt->Location + '\n';
19189  LSTVFile << LSTVIt->ServiceAndRepeatNum + '\n';
19190  LSTVFile << "AtLocTime = " + LSTVIt->AtLocTime + '\n';
19191  LSTVFile << "ArrTime = " + LSTVIt->ArrTime + '\n';
19192  LSTVFile << "DepTime = " + LSTVIt->DepTime + '\n';
19193  if(LSTVIt->FrhMarker == "")
19194  {
19195  LSTVFile << "Not Frh\n";
19196  }
19197  else
19198  {
19199  LSTVFile << LSTVIt->FrhMarker + '\n';
19200  }
19201  LSTVFile << '\n';
19202  }
19203  LSTVFile.close();
19204  Utilities->CallLogPop();
19205  return(true);
19206 //end of debugging section
19207 */
19208  //declare pointers for use in printouts
19209  TLocServiceTimesVector::iterator Ptr1, Ptr2;
19210 
19211  //set up the output file
19212  AnsiString TTFileName3 = TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss");
19213  TTFileName3 = CurDir + "\\Formatted timetables\\Conflict Analysis " + TTFileName3 + "; " + RailwayTitle + "; " + TimetableTitle + ".csv";
19214 
19215  std::ofstream TTFile3(TTFileName3.c_str());
19216 
19217  if(TTFile3 == 0)
19218  {
19219  ShowMessage("Conflict Analysis file failed to open - can't be created");
19220  Utilities->CallLogPop(2210);
19221  return(false);
19222  }
19223  if(LocServiceTimesVector.empty())
19224  {
19225  ShowMessage("No timetabled services found");
19226  TTFile3.close();
19227  DeleteFile(TTFileName3);
19228  Utilities->CallLogPop(2211);
19229  return(false);
19230  }
19231  TTFile3 << "Timetable analysis for timetable: '" + TimetableTitle + "' in conjunction with railway: '" + RailwayTitle + "'\n";
19232  TTFile3 << "See user manual or on-screen help section 5.12 for detailed information.\n\n\n";
19233  SequenceLog += "6\n";
19234 
19235 /*
19236 //print out TrainDataVectorCopy & TrainDataVector for debugging purposes, TrainDataVectorCopy first
19237 
19238 // Double crosslink (shuttle) table:
19239 //Command Format OtherHead NonRepeating- LinkTrain- NonRepeating- Decsription
19240 // Code ShuttleLink- EntryPtr ShuttleLink-
19241 // HeadCode EntryPtr
19242 
19243 //Snt-sh SNTShuttle Y (rtn shuttle) N Y (rtn sh) N Simple shuttle - no feeder service
19244 //Frh-sh TimeCmdHeadCode Y (outwd shuttle) N Y (outwd sh) N Simple shuttle - no finishing service
19245 //F-nshs FNSNonRepeatToShuttle N (shld be Y for outwd shuttle) Y (shld be N) Y (correct) N (correct) Feeder service link to shuttle
19246 //Sns-sh SNSShuttle Y (rtn shuttle) Y (feeder) Y (rtn) Y (fdr) Shuttle link from feeder service
19247 //Sns-fsh SNSNonRepeatFromShuttle N (shld be Y for rtn shuttle) Y (shld be N) Y (correct) N (correct) Finishing service link from shuttle
19248 //Fns-sh FSHNewService Y (outwd shuttle) Y (finishing) Y (outwd sh) Y (finish) Shuttle link to finishing service
19249 //
19250 //Note: Any shuttle start can have any finish - feeder and finish, neither, feeder but no finish & vice versa.
19251 
19252 //NOTE: from above for F-nshs & Sns-fsh in the TrainDataVectorCopy the OtherLinkedHeadCode will be correct as it is derived from LinkTrainEntryPtr which is correct
19253 //but for the original TrainDataVector the OtherLinkedHeadCode will be incorrect, hence have to use the NonRepeatingShuttleLinkHeadCode as that is the correct one
19254 //these were errors when first coded but work ok, just keep in mind when making any changes
19255 
19256 std::ofstream TDVCFile((CurDir + "\\Formatted timetables\\Conflict Analysis " + TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss") + "; " + RailwayTitle + "; " + TimetableTitle + " TrainDataVectorCopy.txt").c_str());
19257 AnsiString OHC = "", NRHC = "";
19258 AnsiString OLk = "", NRLk = "";
19259 TDVCFile << "Note that for the TrainDataVectorCopy OH and OLk should always be the same, as OH is derived from OHLk; and similarly for NR and NRLk\n\n";
19260 for(TTrainDataVector::iterator TDVCIt = TrainDataVectorCopy.begin(); TDVCIt != TrainDataVectorCopy.end(); TDVCIt++)
19261 {
19262  TDVCFile << TDVCIt->ServiceReference + '\n';
19263  TDVCFile << TDVCIt->Description + '\n';
19264  for(unsigned int x = 0; x < TDVCIt->ActionVector.size(); x++)
19265  {
19266  TActionVectorEntry AVE = TDVCIt->ActionVector.at(x);
19267  if(AVE.OtherHeadCode == "")
19268  {
19269  OHC = "OH 0";
19270  }
19271  else
19272  {
19273  OHC = "OH " + AVE.OtherHeadCode;
19274  }
19275  if(AVE.NonRepeatingShuttleLinkHeadCode == "")
19276  {
19277  NRHC = "NR 0";
19278  }
19279  else
19280  {
19281  NRHC = "NR " + AVE.NonRepeatingShuttleLinkHeadCode;
19282  }
19283  if(TDVCIt->ActionVector.at(x).LinkedTrainEntryPtr == 0)
19284  {
19285  OLk = "OLk 0";
19286  }
19287  else
19288  {
19289  OLk = "OLk " + TDVCIt->ActionVector.at(x).LinkedTrainEntryPtr->ServiceReference;
19290  }
19291  if(TDVCIt->ActionVector.at(x).NonRepeatingShuttleLinkEntryPtr == 0)
19292  {
19293  NRLk = "NRLk 0";
19294  }
19295  else
19296  {
19297  NRLk = "NRLk " + TDVCIt->ActionVector.at(x).NonRepeatingShuttleLinkEntryPtr->ServiceReference;
19298  }
19299 
19300  if(AVE.FormatType == TimeCmd) //cdt only
19301  {
19302  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << '\n';
19303  }
19304  if((AVE.FormatType == TimeCmdHeadCode) || (AVE.FormatType == FNSNonRepeatToShuttle))
19305  {
19306  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
19307  }
19308  else if((AVE.FormatType == FSHNewService) || (AVE.FormatType == SNSShuttle)) //these should have 2 linked services
19309  {
19310  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
19311  }
19312  else if(AVE.FormatType == SNSNonRepeatFromShuttle)
19313  {
19314  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
19315  }
19316  else if(AVE.FormatType == StartNew)
19317  {
19318  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << " Snt RearStartID " << Track->TrackElementAt(-1, AVE.RearStartOrRepeatMins).ElementID
19319  << " FrontStartID " << Track->TrackElementAt(-2, AVE.FrontStartOrRepeatDigits).ElementID << '\n';
19320  }
19321  else if(AVE.FormatType == SNTShuttle)
19322  {
19323  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << " Snt-sh RearStartID " << Track->TrackElementAt(-3, AVE.RearStartOrRepeatMins).ElementID
19324  << " FrontStartID " << Track->TrackElementAt(-4, AVE.FrontStartOrRepeatDigits).ElementID << '\n';
19325  }
19326  else if((AVE.FormatType == TimeLoc) && (AVE.ArrivalTime != TDateTime(-1)))
19327  {
19328  TDVCFile << Utilities->Format96HHMM(AVE.ArrivalTime) << " Arr " << AVE.LocationName << '\n';
19329  }
19330  else if((AVE.FormatType == TimeLoc) && (AVE.DepartureTime != TDateTime(-1)))
19331  {
19332  TDVCFile << Utilities->Format96HHMM(AVE.DepartureTime) << " Dep " << AVE.LocationName << '\n';
19333  }
19334  else if(AVE.FormatType == TimeTimeLoc)
19335  {
19336  TDVCFile << Utilities->Format96HHMM(AVE.ArrivalTime) << ' ' << Utilities->Format96HHMM(AVE.DepartureTime) << ' ' << AVE.LocationName << '\n';
19337  }
19338  else if(AVE.FormatType == PassTime)
19339  {
19340  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << "Pass" << ' ' << AVE.LocationName << '\n';
19341  }
19342  else if(AVE.FormatType == ExitRailway)
19343  {
19344  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << " Fer" << '\n';
19345  }
19346  else if(AVE.FormatType == FinRemHere)
19347  {
19348  TDVCFile << "Frh" << '\n';
19349  }
19350  }
19351  TDVCFile << '\n';
19352 }
19353 TDVCFile.close();
19354 
19355 //print out original TrainDataVector for comparison
19356 std::ofstream TDVFile((CurDir + "\\Formatted timetables\\Conflict Analysis " + TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss") + "; " + RailwayTitle + "; " + TimetableTitle + " TrainDataVector.txt").c_str());
19357 TDVFile << "Note that in the TrainDataVector, non-repeating shuttle link services F-nshs and Sns-fsh use the non-repeating headcode (NR) values for the corresponding "
19358  "shuttle headcodes when it should be the other headcode (OH), and the other headcode is unused. The link values are the right way round. Also OH & NR "
19359  "values ARE headcodes and not service references, but OLk and NRLk values are service references.\n\n";
19360 //F-nshs FNSNonRepeatToShuttle N (shld be Y for outwd shuttle) Y (shld be N) Y (correct) N (correct) Feeder service link to shuttle
19361 //Sns-fsh SNSNonRepeatFromShuttle N (shld be Y for rtn shuttle) Y (shld be N) Y (correct) N (correct) Finishing service link from shuttle
19362 for(TTrainDataVector::iterator TDVIt = TrainDataVector.begin(); TDVIt != TrainDataVector.end(); TDVIt++)
19363 {
19364  TDVFile << TDVIt->ServiceReference + '\n';
19365  TDVFile << TDVIt->Description + '\n';
19366  for(unsigned int x = 0; x < TDVIt->ActionVector.size(); x++)
19367  {
19368  TActionVectorEntry AVE = TDVIt->ActionVector.at(x);
19369  if(AVE.OtherHeadCode == "")
19370  {
19371  OHC = "OH 0";
19372  }
19373  else
19374  {
19375  OHC = "OH " + AVE.OtherHeadCode;
19376  }
19377  if(AVE.NonRepeatingShuttleLinkHeadCode == "")
19378  {
19379  NRHC = "NR 0";
19380  }
19381  else
19382  {
19383  NRHC = "NR " + AVE.NonRepeatingShuttleLinkHeadCode;
19384  }
19385  if(TDVIt->ActionVector.at(x).LinkedTrainEntryPtr == 0)
19386  {
19387  OLk = "OLk 0";
19388  }
19389  else
19390  {
19391  OLk = "OLk " + TDVIt->ActionVector.at(x).LinkedTrainEntryPtr->ServiceReference;
19392  }
19393  if(TDVIt->ActionVector.at(x).NonRepeatingShuttleLinkEntryPtr == 0)
19394  {
19395  NRLk = "NRLk 0";
19396  }
19397  else
19398  {
19399  NRLk = "NRLk " + TDVIt->ActionVector.at(x).NonRepeatingShuttleLinkEntryPtr->ServiceReference;
19400  }
19401 
19402  if(AVE.FormatType == TimeCmd) //cdt only
19403  {
19404  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << '\n';
19405  }
19406  if((AVE.FormatType == TimeCmdHeadCode) || (AVE.FormatType == FNSNonRepeatToShuttle))
19407  {
19408  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
19409  }
19410  else if((AVE.FormatType == FSHNewService) || (AVE.FormatType == SNSShuttle)) //these should have 2 linked services
19411  {
19412  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
19413  }
19414  else if(AVE.FormatType == SNSNonRepeatFromShuttle)
19415  {
19416  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
19417  }
19418  else if(AVE.FormatType == StartNew)
19419  {
19420  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << " Snt RearStartID " << Track->TrackElementAt(-5, AVE.RearStartOrRepeatMins).ElementID
19421  << " FrontStartID " << Track->TrackElementAt(-6, AVE.FrontStartOrRepeatDigits).ElementID << '\n';
19422  }
19423  else if(AVE.FormatType == SNTShuttle)
19424  {
19425  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << " Snt-sh RearStartID " << Track->TrackElementAt(-7, AVE.RearStartOrRepeatMins).ElementID
19426  << " FrontStartID " << Track->TrackElementAt(-8, AVE.FrontStartOrRepeatDigits).ElementID << '\n';
19427  }
19428  else if((AVE.FormatType == TimeLoc) && (AVE.ArrivalTime != TDateTime(-1)))
19429  {
19430  TDVFile << Utilities->Format96HHMM(AVE.ArrivalTime) << " Arr " << AVE.LocationName << '\n';
19431  }
19432  else if((AVE.FormatType == TimeLoc) && (AVE.DepartureTime != TDateTime(-1)))
19433  {
19434  TDVFile << Utilities->Format96HHMM(AVE.DepartureTime) << " Dep " << AVE.LocationName << '\n';
19435  }
19436  else if(AVE.FormatType == TimeTimeLoc)
19437  {
19438  TDVFile << Utilities->Format96HHMM(AVE.ArrivalTime) << ' ' << Utilities->Format96HHMM(AVE.DepartureTime) << ' ' << AVE.LocationName << '\n';
19439  }
19440  else if(AVE.FormatType == PassTime)
19441  {
19442  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << "Pass" << ' ' << AVE.LocationName << '\n';
19443  }
19444  else if(AVE.FormatType == ExitRailway)
19445  {
19446  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << " Fer" << '\n';
19447  }
19448  else if(AVE.FormatType == FinRemHere)
19449  {
19450  TDVFile << "Frh" << '\n';
19451  }
19452  }
19453  TDVFile << '\n';
19454 }
19455 TDVFile.close();
19456 //end of debugging
19457 */
19458  //arrivals
19459  if(ArrChecked)
19460  {
19461  //sort in ArrTime order for each location
19462  Ptr1 = LocServiceTimesVector.begin();
19463  Ptr2 = Ptr1 + 1;
19464  while(Ptr2 != LocServiceTimesVector.end())
19465  {
19466  while(Ptr2->Location == Ptr1->Location) //ends with Ptr2 one past same Location value as Ptr1
19467  {
19468  Ptr2++;
19469  if(Ptr2 == LocServiceTimesVector.end())
19470  {
19471  break;
19472  }
19473  }
19474  std::sort(Ptr1, Ptr2, &LocServiceTimesArrTimeSort);
19475  Ptr1 = Ptr2; //first entry with next name
19476  if(Ptr2 != LocServiceTimesVector.end())
19477  {
19478  Ptr2++;
19479  }
19480  }
19481 
19482  //routine for arrivals - number of trains arriving within the specified range with services listed at the end
19483 
19484  TTFile3 << "Arrival & pass analysis: an asterisk means that the number of same approach code arrivals and passes is equal to or greater than the number of platforms.\n";
19485  TTFile3 << "If the total number of arrivals and passes at the same time exceeds the number of platforms the 'Trains present at location analysis' will show an asterisk.\n\n";
19486  MinuteString = " minutes";
19487  AnsiString ServiceAndRepeatNumTotal = "", ServiceAndRepeatNumTotalOutput = "";
19488  if(ArrRange == 1)
19489  {
19490  MinuteString = " minute";
19491  }
19492  TTFile3 << "Location,Number of,Number of,Services arriving within " << AnsiString(ArrRange) << MinuteString << " with their arrival times and approach codes\n";
19493  TTFile3 << ",Platforms,Trains\n\n";
19494 
19495  Ptr1 = LocServiceTimesVector.begin();
19496  Ptr2 = Ptr1 + 1;
19497  while(Ptr2 != LocServiceTimesVector.end())
19498  {
19499  PreviousService = "";
19500  NumTrainsAtLoc = 0;
19501  ServiceAndRepeatNumTotal = "";
19502  NumPlats = 0;
19503  NumPlatsAtThisLocCalculated = false;
19504  BasicTime = "";
19505  while((Ptr2->Location != Ptr1->Location) || ((Ptr1->Location == "") && (Ptr2->Location == "")))
19506  {
19507  PreviousService = "";
19508  NumTrainsAtLoc = 0;
19509  ServiceAndRepeatNumTotal = "";
19510  NumPlats = 0;
19511  NumPlatsAtThisLocCalculated = false;
19512  BasicTime = "";
19513  Ptr1++;
19514  Ptr2++;
19515  if(Ptr2 == LocServiceTimesVector.end())
19516  {
19517  break;
19518  }
19519  }
19520  if(Ptr2 == LocServiceTimesVector.end())
19521  {
19522  break;
19523  }
19524  while(Ptr2->Location == Ptr1->Location)
19525  {
19526  PreviousService = "";
19527  NumTrainsAtLoc = 0;
19528  ServiceAndRepeatNumTotal = "";
19529  BasicTime = Ptr1->ArrTime; //used to compare later times - later pointer contents have same or later times as sorted in time order
19530  if((Ptr1->Location == "") && (Ptr2->Location == ""))
19531  {
19532  break;
19533  }
19534  while(!WithinTimeRange(0, BasicTime, Ptr2->ArrTime, ArrRange) || ((Ptr1->ArrTime == "") && (Ptr2->ArrTime == "")))
19535  {
19536  BasicTime = Ptr2->ArrTime; //used to compare later times or last can exceed first
19537  Ptr1++;
19538  Ptr2++;
19539  if(Ptr2 == LocServiceTimesVector.end())
19540  {
19541  break;
19542  }
19543  if(Ptr2->Location != Ptr1->Location)
19544  {
19545  break;
19546  }
19547  }
19548  if(Ptr2 == LocServiceTimesVector.end())
19549  {
19550  break;
19551  }
19552  if(Ptr2->Location != Ptr1->Location)
19553  {
19554  break;
19555  }
19556  while(WithinTimeRange(1, BasicTime, Ptr2->ArrTime, ArrRange))
19557  {
19558  if((Ptr1->ArrTime == "") && (Ptr2->ArrTime == ""))
19559  {
19560  break;
19561  }
19562  if(!NumPlatsAtThisLocCalculated) //num plats at relevant location, reset when locations change
19563  {
19564  NumPlats = Track->NumberOfPlatforms(0, Ptr1->Location);
19565  NumPlatsAtThisLocCalculated = true;
19566  }
19567  if(Ptr1->ServiceAndRepeatNum != PreviousService) //don't print it twice if same as last - as will be if >1 service at same loc at same time
19568  {
19569  if(ServiceAndRepeatNumTotal == "")
19570  {
19571  ServiceAndRepeatNumTotal = Ptr1->ServiceAndRepeatNum + "," + Ptr1->ArrTime;
19572  NumTrainsAtLoc = 1;
19573  }
19574  else
19575  {
19576  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr1->ServiceAndRepeatNum + "," + Ptr1->ArrTime;
19577  }
19578  }
19579  PreviousService = Ptr2->ServiceAndRepeatNum; //last service at relevant time, reset when times differ
19580  if(ServiceAndRepeatNumTotal == "")
19581  {
19582  ServiceAndRepeatNumTotal = Ptr2->ServiceAndRepeatNum + "," + Ptr2->ArrTime;
19583  NumTrainsAtLoc = 1;
19584  }
19585  else
19586  {
19587  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr2->ServiceAndRepeatNum + "," + Ptr2->ArrTime;
19588  }
19589  Ptr1 = Ptr2;
19590  Ptr2++;
19591  if((Ptr2 == LocServiceTimesVector.end()) || (Ptr2->Location != Ptr1->Location) || (!WithinTimeRange(2, BasicTime, Ptr2->ArrTime, ArrRange)))
19592  {
19593  int MaxNumberOfSameDirections = 0;
19594  ServiceAndRepeatNumTotalOutput = ConsolidateSARNTArrDep(1, ServiceAndRepeatNumTotal, NumTrainsAtLoc, Ptr1->Location, true, AnalysisError, MaxNumberOfSameDirections); //sort into alphabetical order and remove duplicates
19595  if(AnalysisError) //has to be Ptr1->Location as Ptr2 loc may have changed
19596  {
19597 // ShowMessage("Error in arrival analysis - file will be incomplete and/or corrupt. Please send railway and timetable files to railwayfeedback@gmail.com for investigation - thanks. Details: " + ServiceAndRepeatNumTotalOutput);
19598  TTFile3.close();
19599  throw Exception(ServiceAndRepeatNumTotalOutput.c_str());
19600 // Utilities->CallLogPop(2224);
19601 // return false;
19602  }
19603  AnsiString Asterisk = "";
19604  if(MaxNumberOfSameDirections >= NumPlats)
19605  {
19606  Asterisk = "* ";
19607  }
19608  //print out a single line for number of trains at loc with all service refs
19609  TTFile3 << Asterisk << Ptr1->Location << "," << NumPlats << "," << NumTrainsAtLoc << "," << ServiceAndRepeatNumTotalOutput << '\n'; //no description as >1 service
19610  ArrivalsPrinted = true;
19611  ServiceAndRepeatNumTotal = "";
19612  }
19613  if(Ptr2 == LocServiceTimesVector.end())
19614  {
19615  break;
19616  }
19617  if(Ptr2->Location != Ptr1->Location)
19618  {
19619  break;
19620  }
19621  }
19622  if(Ptr2 == LocServiceTimesVector.end())
19623  {
19624  break;
19625  }
19626  }
19627  }
19628  if(!ArrivalsPrinted)
19629  {
19630  TTFile3 << "Nothing to report for arrivals";
19631  }
19632  TTFile3 << "\n\n";
19633  }
19634  //end of routine for arrivals
19635  SequenceLog += "7\n";
19636  //departures
19637  if(DepChecked)
19638  {
19639  //sort in DepTime order for each location
19640  Ptr1 = LocServiceTimesVector.begin();
19641  Ptr2 = Ptr1 + 1;
19642  while(Ptr2 != LocServiceTimesVector.end())
19643  {
19644  while(Ptr2->Location == Ptr1->Location) //ends with Ptr2 one past same Location value as Ptr1
19645  {
19646  Ptr2++;
19647  if(Ptr2 == LocServiceTimesVector.end())
19648  {
19649  break;
19650  }
19651  }
19652  std::sort(Ptr1, Ptr2, &LocServiceTimesDepTimeSort);
19653  Ptr1 = Ptr2; //first entry with next name
19654  if(Ptr2 != LocServiceTimesVector.end())
19655  {
19656  Ptr2++;
19657  }
19658  }
19659 
19660  //routine for departures - number of trains departing within the specified range with services listed at the end
19661  TTFile3 << "Departure & pass analysis: an asterisk means that the number of same exit code departures and passes is equal to or greater than the number of platforms.\n";
19662  TTFile3 << "If the total number of departures and passes at the same time exceeds the number of platforms the 'Trains present at location analysis' will show an asterisk.\n\n";
19663  MinuteString = " minutes";
19664  AnsiString ServiceAndRepeatNumTotal = "", ServiceAndRepeatNumTotalOutput = "";
19665  if(DepRange == 1)
19666  {
19667  MinuteString = " minute";
19668  }
19669  TTFile3 << "Location,Number of,Number of,Services departing within " << AnsiString(DepRange) << MinuteString << " with their departure times and exit codes\n";
19670  TTFile3 << ",Platforms,Trains\n\n";
19671 
19672  Ptr1 = LocServiceTimesVector.begin();
19673  Ptr2 = Ptr1 + 1;
19674  while(Ptr2 != LocServiceTimesVector.end())
19675  {
19676  PreviousService = "";
19677  NumTrainsAtLoc = 0;
19678  ServiceAndRepeatNumTotal = "";
19679  NumPlats = 0;
19680  NumPlatsAtThisLocCalculated = false;
19681  BasicTime = "";
19682  while((Ptr2->Location != Ptr1->Location) || ((Ptr1->Location == "") && (Ptr2->Location == "")))
19683  {
19684  PreviousService = "";
19685  NumTrainsAtLoc = 0;
19686  ServiceAndRepeatNumTotal = "";
19687  NumPlats = 0;
19688  NumPlatsAtThisLocCalculated = false;
19689  BasicTime = "";
19690  Ptr1++;
19691  Ptr2++;
19692  if(Ptr2 == LocServiceTimesVector.end())
19693  {
19694  break;
19695  }
19696  }
19697  if(Ptr2 == LocServiceTimesVector.end())
19698  {
19699  break;
19700  }
19701  while(Ptr2->Location == Ptr1->Location)
19702  {
19703  PreviousService = "";
19704  NumTrainsAtLoc = 0;
19705  ServiceAndRepeatNumTotal = "";
19706  BasicTime = Ptr1->DepTime; //used to compare later times - later pointer contents have same or later times as sorted in time order
19707  if((Ptr1->Location == "") && (Ptr2->Location == ""))
19708  {
19709  break;
19710  }
19711  while(!WithinTimeRange(3, BasicTime, Ptr2->DepTime, DepRange) || ((Ptr1->DepTime == "") && (Ptr2->DepTime == "")))
19712  {
19713  BasicTime = Ptr2->DepTime; //used to compare later times or last can exceed first
19714  Ptr1++;
19715  Ptr2++;
19716  if(Ptr2 == LocServiceTimesVector.end())
19717  {
19718  break;
19719  }
19720  if(Ptr2->Location != Ptr1->Location)
19721  {
19722  break;
19723  }
19724  }
19725  if(Ptr2 == LocServiceTimesVector.end())
19726  {
19727  break;
19728  }
19729  if(Ptr2->Location != Ptr1->Location)
19730  {
19731  break;
19732  }
19733  while(WithinTimeRange(4, BasicTime, Ptr2->DepTime, DepRange))
19734  {
19735  if((Ptr1->DepTime == "") && (Ptr2->DepTime == ""))
19736  {
19737  break;
19738  }
19739  if(!NumPlatsAtThisLocCalculated) //num plats at relevant location, reset when locations change
19740  {
19741  NumPlats = Track->NumberOfPlatforms(1, Ptr1->Location);
19742  NumPlatsAtThisLocCalculated = true;
19743  }
19744  if(Ptr1->ServiceAndRepeatNum != PreviousService) //don't print it twice if same as last - as will be if >1 service at same loc at same time
19745  {
19746  if(ServiceAndRepeatNumTotal == "")
19747  {
19748  ServiceAndRepeatNumTotal = Ptr1->ServiceAndRepeatNum + "," + Ptr1->DepTime;
19749  NumTrainsAtLoc = 1;
19750  }
19751  else
19752  {
19753  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr1->ServiceAndRepeatNum + "," + Ptr1->DepTime;
19754  }
19755  }
19756  PreviousService = Ptr2->ServiceAndRepeatNum; //last service at relevant time, reset when times differ
19757  if(ServiceAndRepeatNumTotal == "")
19758  {
19759  ServiceAndRepeatNumTotal = Ptr2->ServiceAndRepeatNum + "," + Ptr2->DepTime;
19760  NumTrainsAtLoc = 1;
19761  }
19762  else
19763  {
19764  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr2->ServiceAndRepeatNum + "," + Ptr2->DepTime;
19765  }
19766  Ptr1 = Ptr2;
19767  Ptr2++;
19768  if((Ptr2 == LocServiceTimesVector.end()) || (Ptr2->Location != Ptr1->Location) || (!WithinTimeRange(5, BasicTime, Ptr2->DepTime, DepRange)))
19769  {
19770  int MaxNumberOfSameDirections = 0;
19771  ServiceAndRepeatNumTotalOutput = ConsolidateSARNTArrDep(3, ServiceAndRepeatNumTotal, NumTrainsAtLoc, Ptr1->Location, false, AnalysisError, MaxNumberOfSameDirections); //sort into alphabetical order and remove duplicates
19772  if(AnalysisError) //has to be Ptr1->Location as Ptr2 loc may have changed
19773  {
19774 // ShowMessage("Error in departure analysis - file will be incomplete and/or corrupt. Please send railway and timetable files to railwayfeedback@gmail.com for investigation - thanks. Details: " + ServiceAndRepeatNumTotalOutput);
19775  TTFile3.close();
19776  throw Exception(ServiceAndRepeatNumTotalOutput.c_str());
19777 // Utilities->CallLogPop(2225);
19778 // return false;
19779  }
19780  AnsiString Asterisk = "";
19781  if(MaxNumberOfSameDirections >= NumPlats)
19782  {
19783  Asterisk = "* ";
19784  }
19785  //print out a single line for number of trains at loc with all service refs
19786  TTFile3 << Asterisk << Ptr1->Location << "," << NumPlats << "," << NumTrainsAtLoc << "," << ServiceAndRepeatNumTotalOutput << '\n'; //no description as >1 service
19787  DeparturesPrinted = true;
19788  ServiceAndRepeatNumTotal = "";
19789  }
19790  if(Ptr2 == LocServiceTimesVector.end())
19791  {
19792  break;
19793  }
19794  if(Ptr2->Location != Ptr1->Location)
19795  {
19796  break;
19797  }
19798  }
19799  if(Ptr2 == LocServiceTimesVector.end())
19800  {
19801  break;
19802  }
19803  }
19804  }
19805  if(!DeparturesPrinted)
19806  {
19807  TTFile3 << "Nothing to report for departures";
19808  }
19809  TTFile3 << "\n\n";
19810  }
19811  //end of routine for departures
19812  SequenceLog += "8\n";
19813 
19814  //list trains at locations at same time
19815 
19816  if(AtLocChecked)
19817  {
19818  //sort in AtLocTime order for each location
19819  Ptr1 = LocServiceTimesVector.begin();
19820  Ptr2 = Ptr1 + 1;
19821  while(Ptr2 != LocServiceTimesVector.end())
19822  {
19823  while(Ptr2->Location == Ptr1->Location) //ends with Ptr2 one past same Location value as Ptr1
19824  {
19825  Ptr2++;
19826  if(Ptr2 == LocServiceTimesVector.end())
19827  {
19828  break;
19829  }
19830  }
19831  std::sort(Ptr1, Ptr2, &LocServiceTimesAtLocTimeSort);
19832  Ptr1 = Ptr2; //first entry with next name
19833  if(Ptr2 != LocServiceTimesVector.end())
19834  {
19835  Ptr2++;
19836  }
19837  }
19838 
19839  //print out simultaneous AtLocs (don't need range of times for AtLocs)
19840  TTFile3 << "Trains present at location analysis: an asterisk means that the number of trains at the location is greater than the number of platforms.\n\n";
19841  TTFile3 << "Location,Number of,Number of,Time,Services at the location at that time\n";
19842  TTFile3 << ",Platforms,Trains,\n\n";
19843  AnsiString ServiceAndRepeatNumTotal = "", ServiceAndRepeatNumTotalOutput = "";
19844  Ptr1 = LocServiceTimesVector.begin();
19845  Ptr2 = Ptr1 + 1;
19846  while(Ptr2 != LocServiceTimesVector.end())
19847  {
19848  PreviousService = "";
19849  ServiceAndRepeatNumTotal = "";
19850  NumTrainsAtLoc = 0;
19851  NumPlats = 0;
19852  NumPlatsAtThisLocCalculated = false;
19853  FrhCount = 0;
19854 
19855  while((Ptr2->Location != Ptr1->Location) || ((Ptr1->Location == "") && (Ptr2->Location == "")))
19856  {
19857  PreviousService = "";
19858  ServiceAndRepeatNumTotal = "";
19859  NumTrainsAtLoc = 0;
19860  NumPlats = 0;
19861  NumPlatsAtThisLocCalculated = false;
19862  FrhCount = 0;
19863  Ptr1++;
19864  Ptr2++;
19865  if(Ptr2 == LocServiceTimesVector.end())
19866  {
19867  break;
19868  }
19869  }
19870  if(Ptr2 == LocServiceTimesVector.end())
19871  {
19872  break;
19873  }
19874  while(Ptr2->Location == Ptr1->Location)
19875  {
19876  if(Ptr1->FrhMarker == "Frh") //this test is made here and each time Ptr1 increases with Ptr1 & 2 at same loc so as to catch them all
19877  {
19878  FrhCount++;
19879  Ptr1->FrhMarker = "FrhCounted"; //to avoid double counting
19880  }
19881  PreviousService = "";
19882  NumTrainsAtLoc = 0;
19883  ServiceAndRepeatNumTotal = "";
19884  if((Ptr1->Location == "") && (Ptr2->Location == ""))
19885  {
19886  break;
19887  }
19888  while((Ptr2->AtLocTime != Ptr1->AtLocTime) || ((Ptr1->AtLocTime == "") && (Ptr2->AtLocTime == "")))
19889  {
19890  Ptr1++;
19891  if(Ptr1->FrhMarker == "Frh")
19892  {
19893  FrhCount++;
19894  Ptr1->FrhMarker = "FrhCounted"; //to avoid double counting
19895  }
19896  Ptr2++;
19897  if(Ptr2 == LocServiceTimesVector.end())
19898  {
19899  break;
19900  }
19901  if(Ptr2->Location != Ptr1->Location)
19902  {
19903  break;
19904  }
19905  }
19906  if(Ptr2 == LocServiceTimesVector.end())
19907  {
19908  break;
19909  }
19910  if(Ptr2->Location != Ptr1->Location)
19911  {
19912  break;
19913  }
19914  while(Ptr2->AtLocTime == Ptr1->AtLocTime)
19915  {
19916  if((Ptr1->AtLocTime == "") && (Ptr2->AtLocTime == ""))
19917  {
19918  break;
19919  }
19920  if(!NumPlatsAtThisLocCalculated) //num plats at relevant location, reset when locations change
19921  {
19922  NumPlats = Track->NumberOfPlatforms(2, Ptr1->Location);
19923  NumPlatsAtThisLocCalculated = true;
19924  }
19925  if(Ptr1->ServiceAndRepeatNum != PreviousService) //don't print it twice if same as last - as will be if >1 service at same loc at same time
19926  {
19927  if(ServiceAndRepeatNumTotal == "")
19928  {
19929  ServiceAndRepeatNumTotal = Ptr1->ServiceAndRepeatNum;
19930  NumTrainsAtLoc = 1;
19931  }
19932  else
19933  {
19934  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr1->ServiceAndRepeatNum;
19935  }
19936  }
19937  PreviousService = Ptr2->ServiceAndRepeatNum; //last service at relevant time, reset when times differ, has to be Ptr2 to compare Ptr1 at next round when incremented
19938  if(ServiceAndRepeatNumTotal == "")
19939  {
19940  ServiceAndRepeatNumTotal = Ptr2->ServiceAndRepeatNum;
19941  NumTrainsAtLoc = 1;
19942  }
19943  else
19944  {
19945  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr2->ServiceAndRepeatNum;
19946  }
19947  Ptr1 = Ptr2;
19948  if(Ptr1->FrhMarker == "Frh")
19949  {
19950  FrhCount++;
19951  Ptr1->FrhMarker = "FrhCounted"; //to avoid double counting
19952  }
19953  Ptr2++;
19954  if((Ptr2 == LocServiceTimesVector.end()) || (Ptr2->Location != Ptr1->Location) || (Ptr2->AtLocTime != Ptr1->AtLocTime))
19955  {
19956 //old text //only print out if no remainers (1st condition), change in remainers (2nd condition) or change in ServiceAndRepeatNumTotalOutput, and >1 train (later condition)
19957 //new text //don't print out if all remainers or if only 1 train at loc
19958  ServiceAndRepeatNumTotalOutput = ConsolidateSARNTAtLoc(1, ServiceAndRepeatNumTotal, NumTrainsAtLoc); //sort into alphabetical order, remove duplicates, and calculate new value for NumTrainsAtLoc
19959 //old condits if((FrhCount == 0) || (FrhCount != LastFrhCount) || (PreviousServiceAndRepeatNumTotalOutput != ServiceAndRepeatNumTotalOutput))//don't print if same output
19960 /*new condits*/ if((NumTrainsAtLoc > 1) && ((FrhCount < NumTrainsAtLoc) || (FrhCount != LastFrhCount)))
19961  {
19962  AnsiString Asterisk = "";
19963  if(NumTrainsAtLoc > NumPlats)
19964  {
19965  Asterisk = "* ";
19966  }
19967  //print out a single line for number of trains at loc with all service refs
19968  if(FrhCount == 0)
19969  {
19970  TTFile3 << Asterisk << Ptr1->Location << "," << NumPlats << "," << NumTrainsAtLoc << "," << Ptr1->AtLocTime << "," << ServiceAndRepeatNumTotalOutput << '\n';
19971  }
19972  else if(FrhCount == 1)
19973  {
19974  TTFile3 << Asterisk << Ptr1->Location << "," << NumPlats << "," << NumTrainsAtLoc << "," << Ptr1->AtLocTime << " (1 remains here)," << ServiceAndRepeatNumTotalOutput << '\n';
19975  }
19976  else
19977  {
19978  TTFile3 << Asterisk << Ptr1->Location << "," << NumPlats << "," << NumTrainsAtLoc << "," << Ptr1->AtLocTime << " (" << FrhCount << " remain here)," << ServiceAndRepeatNumTotalOutput << '\n';
19979  }
19980  LastFrhCount = FrhCount;
19981  PreviousServiceAndRepeatNumTotalOutput = ServiceAndRepeatNumTotalOutput;
19982  AtLocsPrinted = true;
19983  ServiceAndRepeatNumTotal = "";
19984  }
19985  }
19986  if(Ptr2 == LocServiceTimesVector.end())
19987  {
19988  break;
19989  }
19990  if(Ptr2->Location != Ptr1->Location)
19991  {
19992  break;
19993  }
19994  }
19995  if(Ptr2 == LocServiceTimesVector.end())
19996  {
19997  break;
19998  }
19999  }
20000  }
20001  if(!AtLocsPrinted)
20002  {
20003  TTFile3 << "Nothing to report for trains at locations";
20004  }
20005  TTFile3 << "\n\n";
20006  //end of simultaneous AtLocs
20007  }
20008  SequenceLog += "9\n";
20009 
20010 /*
20011 //start of debugging section
20012  //print out the full vector here for testing purposes
20013  TTFile3 << "Full LocServiceTimesVector\n\n";
20014  TTFile3 << "Location,AtLocTime,ArrTime,DepTime,ServiceAndRepeatNum,Description\n\n";
20015 
20016  for(TLocServiceTimesVector::iterator Ptr = LocServiceTimesVector.begin(); Ptr != LocServiceTimesVector.end(); Ptr++)
20017  {
20018  TTFile3 << Ptr->Location << "," << Ptr->AtLocTime << "," << Ptr->ArrTime << "," << Ptr->DepTime << "," << Ptr->ServiceAndRepeatNum << "," << Ptr->FrhMarker << '\n';
20019  }
20020 
20021  TTFile3 << "\n\n\n";
20022 //end of debugging
20023 */
20024 
20025 /*cdt analysis - added at v2.10.0
20026 2 pass system: 1st extract as a single service all Snt (or Snt-sh) starts, with Fns/Sns links combined (and F-nshs/Sns-sh) (though add a new
20027 changeover code [chr XXXX - 'change ref + new reference] until come to Fjo, Frh, Frh-sh, Fer (ignore exit loc as can't stop there), ignore jbos &
20028 repeats, but with fsp & rsp store all the foregoing service entries along with the split reference & add that to the relevant Sfs entry as a new
20029 service. For shuttles with feeder start with feeder & progress into shuttle, ending when finish & remain here or progressing into the finishing
20030 service.
20031 
20032 Use The TrainDataVectorCopy as that has all unique service refs.
20033 
20034 2nd run the cdt checker similar to that in SecondPassActions, but where a same name found either side of a changeover code quote both refs. Add a
20035 similar unexpected cdt check where if have different locs either side of a cdt then may be inappropriate.
20036 
20037 First create a new TrainDataVector from earlier copy as above with single services
20038 */
20039  if(DirChecked)
20040  {
20041  //direction analysis added at v2.10.0
20042  TTrainDataEntry SingleServiceEntry, PartServiceEntry, NewPartServiceEntry, TempEntry;
20043  TTrainDataVector SingleServiceVector, PartServiceVector;
20044 
20045  //find new train services (Snt or Snt-sh) & remember that entries can be in any order
20046  //NB: ALWAYS use OtherHeadCode (which is now a service reference) for any follow-on service
20047  TTFile3 << "Train direction analysis - consisting of train facing directions on creation and possible missing or questionable changes of direction:\n\n";
20048  for(unsigned int x = 0; x < TrainDataVectorCopy.size(); x++)
20049  {
20051  if(TDE.ActionVector.at(TDE.ActionVector.size() - 1).FormatType == Repeat)
20052  {
20053  TDE.ActionVector.erase(TDE.ActionVector.end() - 1); //strip repeat entry if present
20054  }
20055  const TActionVector &AV = TDE.ActionVector;
20056  if((AV.at(0).Command == "Snt") || (AV.at(0).Command == "Snt-sh"))
20057  {
20058  SingleServiceEntry = TDE;
20059  TActionVector &SSAV = SingleServiceEntry.ActionVector;
20060  for(unsigned int y = 0; y < SSAV.size(); y++)
20061  {
20062  if((SSAV.at(y).Command == "Fjo") || (SSAV.at(y).Command == "Frh") || (SSAV.at(y).Command == "Fer") || (SSAV.at(y).Command == "Frh-sh"))
20063  {
20064  SingleServiceVector.push_back(SingleServiceEntry); //push the complete entry
20065  break; //finished with this one
20066  }
20067  else if((SSAV.at(y).Command == "fsp") || (SSAV.at(y).Command == "rsp"))
20068  {
20069  PartServiceEntry = TDE; //start with complete entry
20070  PartServiceEntry.ActionVector.clear(); //clear AV
20071  for(unsigned int z = 0; z <= y; z++)
20072  {
20073  PartServiceEntry.ActionVector.push_back(TDE.ActionVector.at(z)); //add back all AVs up to & inc fsp/rsp
20074  if(z == y)
20075  {
20076  PartServiceEntry.ActionVector.at(z).Command = "chr-sp"; //change split command to chr
20077  PartServiceEntry.ActionVector.at(z).OtherHeadCode = PartServiceEntry.ActionVector.at(z).LinkedTrainEntryPtr->ServiceReference;
20078  }
20079  }
20080  PartServiceVector.push_back(PartServiceEntry);
20081  if(SSAV.at(y).Command == "fsp")
20082  {
20083  SSAV.at(y).Command = "Front split - original service continues below";
20084  SSAV.at(y).OtherHeadCode = "";
20085  }
20086  if(SSAV.at(y).Command == "rsp")
20087  {
20088  SSAV.at(y).Command = "Rear split - original service continues below";
20089  SSAV.at(y).OtherHeadCode = "";
20090  }
20091  //don't break & continue here because the original train carries on
20092  }
20093  else if(SSAV.at(y).Command == "Fns")
20094  {
20095  SSAV.at(y).Command = "chr-Fns";
20096  SSAV.at(y).OtherHeadCode = SSAV.at(y).LinkedTrainEntryPtr->ServiceReference;
20097  PartServiceVector.push_back(SingleServiceEntry); //not complete yet
20098  break; //from y loop
20099  }
20100  else if(SSAV.at(y).Command == "Fns-sh")
20101  {
20102  SSAV.at(y).Command = "chr-Fns-sh";
20103  SSAV.at(y).OtherHeadCode = SSAV.at(y).NonRepeatingShuttleLinkEntryPtr->ServiceReference;
20104  SSAV.at(y).NonRepeatingShuttleLinkHeadCode = "";
20105  PartServiceVector.push_back(SingleServiceEntry); //not complete yet
20106  break; //from y loop
20107  }
20108  else if(SSAV.at(y).Command == "F-nshs")
20109  {
20110  SSAV.at(y).Command = "chr-F-nshs"; //NonRepeatingShuttleLinkHeadCode is the shuttle headcode
20111  SSAV.at(y).OtherHeadCode = SSAV.at(y).LinkedTrainEntryPtr->ServiceReference;
20112  SSAV.at(y).NonRepeatingShuttleLinkHeadCode = "";
20113  PartServiceVector.push_back(SingleServiceEntry); //not complete yet
20114  break; //from y loop
20115  }
20116  }
20117  }
20118  }
20119  SequenceLog += "10\n";
20120  //now have all complete entries in SingleServiceVector and all part services in PartServiceVector but without any follow-ons
20121 
20122  //Now add Sns & Sns-sh services to PartServiceVector entries
20123  AnsiString NextRef;
20124  while(!PartServiceVector.empty())
20125  {
20126  PartServiceEntry = PartServiceVector.at(0); //deal with front entry and add new entries at the back
20127  for(unsigned int y = 0; y < PartServiceEntry.ActionVector.size(); y++)
20128  {
20129  if(PartServiceEntry.ActionVector.at(y).Command.SubString(1,3) == "chr")
20130  {
20131  NextRef = PartServiceEntry.ActionVector.at(y).OtherHeadCode;
20132  }
20133  }
20134  //find it in TrainDataVectorCopy
20135  bool FinishType = true, FoundFlag = false;
20136  while(FinishType)
20137  {
20138  TempEntry = GetServiceFromVector(0, NextRef, TrainDataVectorCopy, FinishType, FoundFlag); //FinishType is a bool where false = Final (Fjo, Frh, Fer, or
20139  //Frh-sh); true = MoreToCome (Fns, Fns-sh, F-nshs)
20140  if(FoundFlag)
20141  {
20142  for(unsigned int y = 1; y < TempEntry.ActionVector.size(); y++) //starts at 1 as that is the entry after the start entry
20143  {
20144  if((TempEntry.ActionVector.at(y).Command == "") && (TempEntry.ActionVector.at(y).FormatType != Repeat))
20145  {
20146  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y));
20147  }
20148  else if((TempEntry.ActionVector.at(y).Command[1] != 'F') && (TempEntry.ActionVector.at(y).Command != "fsp") && (TempEntry.ActionVector.at(y).Command != "rsp") && (TempEntry.ActionVector.at(y).FormatType != Repeat))
20149  {
20150  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y));
20151  }
20152  else
20153  {
20154  if((TempEntry.ActionVector.at(y).Command == "Fjo") || (TempEntry.ActionVector.at(y).Command == "Frh") || (TempEntry.ActionVector.at(y).Command == "Fer") || (TempEntry.ActionVector.at(y).Command == "Frh-sh"))
20155  {
20156  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y));
20157  SingleServiceVector.push_back(PartServiceVector.at(0)); //push the complete entry
20158  PartServiceVector.erase(PartServiceVector.begin());
20159  break; //from y loop
20160  }
20161  else if((TempEntry.ActionVector.at(y).Command == "fsp") || (TempEntry.ActionVector.at(y).Command == "rsp"))
20162  {
20163  NewPartServiceEntry = PartServiceVector.at(0); //covers everything up to but excluding the split
20164  NewPartServiceEntry.ActionVector.push_back(TempEntry.ActionVector.at(y)); //now includes the split
20165  NewPartServiceEntry.ActionVector.at(NewPartServiceEntry.ActionVector.size() - 1).Command = "chr-sp"; //change split command to chr
20166  NewPartServiceEntry.ActionVector.at(NewPartServiceEntry.ActionVector.size() - 1).OtherHeadCode = NewPartServiceEntry.ActionVector.at(NewPartServiceEntry.ActionVector.size() - 1).LinkedTrainEntryPtr->ServiceReference;
20167  PartServiceVector.push_back(NewPartServiceEntry); //new entry for the split service
20168  if(TempEntry.ActionVector.at(y).Command == "fsp")
20169  {
20170  TempEntry.ActionVector.at(y).Command = "Front split - original service continues below";
20171  TempEntry.ActionVector.at(y).OtherHeadCode = "";
20172  }
20173  if(TempEntry.ActionVector.at(y).Command == "rsp")
20174  {
20175  TempEntry.ActionVector.at(y).Command = "Rear split - original service continues below";
20176  TempEntry.ActionVector.at(y).OtherHeadCode = "";
20177  }
20178  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y));
20179  }
20180  else if(TempEntry.ActionVector.at(y).Command == "Fns")
20181  {
20182  TempEntry.ActionVector.at(y).Command = "chr-Fns";
20183  NextRef = TempEntry.ActionVector.at(y).LinkedTrainEntryPtr->ServiceReference;
20184  TempEntry.ActionVector.at(y).OtherHeadCode = NextRef;
20185  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y)); //not complete yet
20186  break; //from y loop
20187  }
20188  else if(TempEntry.ActionVector.at(y).Command == "Fns-sh")
20189  {
20190  TempEntry.ActionVector.at(y).Command = "chr-Fns-sh";
20191  TempEntry.ActionVector.at(y).OtherHeadCode = TempEntry.ActionVector.at(y).NonRepeatingShuttleLinkEntryPtr->ServiceReference;
20192  TempEntry.ActionVector.at(y).NonRepeatingShuttleLinkHeadCode = "";
20193  NextRef = TempEntry.ActionVector.at(y).NonRepeatingShuttleLinkEntryPtr->ServiceReference;
20194  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y)); //not complete yet
20195  break; //from y loop
20196  }
20197  else if(TempEntry.ActionVector.at(y).Command == "F-nshs")
20198  {
20199  TempEntry.ActionVector.at(y).Command = "chr-F-nshs"; //NonRepeatingShuttleLinkHeadCode is the shuttle headcode
20200  TempEntry.ActionVector.at(y).OtherHeadCode = TempEntry.ActionVector.at(y).LinkedTrainEntryPtr->ServiceReference;
20201  TempEntry.ActionVector.at(y).NonRepeatingShuttleLinkHeadCode = "";
20202  NextRef = TempEntry.ActionVector.at(y).LinkedTrainEntryPtr->ServiceReference;
20203  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y)); //not complete yet
20204  break; //from y loop
20205  }
20206  }
20207  }
20208  }
20209  else
20210  {
20211  SequenceLog += + "11\n";
20212  throw Exception("Unable to find service reference " + NextRef + " Last ref checked = " + TempEntry.ServiceReference);
20213  }
20214  }
20215  }
20216  if(!PartServiceVector.empty())
20217  {
20218  SequenceLog += "12\n";
20219  throw Exception("PartServiceVector should be empty here - size = " + AnsiString(PartServiceVector.size()));
20220  }
20221  SequenceLog += "13\n";
20222  /*
20223  form:-
20224  HeadCode[;Description (plain text, no commas or semicolons)][;StartSpeed(kph); MaxRunningSpeed(kph); Mass(tonnes, prog converts to kg);
20225  MaxBrakeRate(tonnes force, prog converts to m/s/s); & gross power(kW, prog converts to power at rail in w)
20226  then multiple entries, separated by commas, of the form:-
20227 
20228  HH:MM;Snt;RearStartIdent FrontStartIdent }StartNew }
20229  HH:MM;Snt-sh;RearStartIdent FrontStartIdent;Fsh HeadCode }SNTShuttle }
20230  HH:MM;Sns-sh;Fxx-sh HeadCode;F-nshs HeadCode (non-repeating)}SNSShuttle }
20231 
20232  HH:MM;Command;HeadCode (Sfs Sns jbo fsp rsp Fns Fjo Frh-sh) }TimeCmdHeadCode } Train action entries
20233  HH:MM;F-nshs;NonRepeatingShuttleLinkHeadCode }FNSNonRepeatToShuttle }
20234  HH:MM;Sns-fsh;NonRepeatingShuttleLinkHeadCode }SNSNonRepeatFromShuttle }
20235 
20236  HH:MM;Command (cdt) }TimeCmd }
20237  HH:MM;Command;new description (dsc) }TimeCmdDescription }
20238  HH:MM;Command;new maximum speed (cms) }TimeCmdMaxSpeed }
20239  HH:MM;Location (arr & dep) }TimeLoc }
20240  HH:MM;HH:MM;Location }TimeTimeLoc }
20241  HH:MM;pas;Location }PassTime }
20242  HH:MM;Fns-sh;Snx-sh HeadCode;Sns-fsh HeadCode (non-rep) }FSHNewService }
20243  HH:MM;Fer;set of allowable IDs }ExitRailway }
20244  Command (Frh only) }FinRemHere }
20245 
20246  R;mm;dd;nn. Repeat Repeat entry
20247 
20248  Formats:
20249 
20250  Command only: Frh
20251  Time;Command: cdt
20252  Time;Command;new description: dsc
20253  Time;Command;new max speed: cms
20254  Time;Command;Headcode: Sfs Sns jbo fsp rsp Fns Fjo Frh-sh F-nshs Sns-fsh
20255  Time;Command;2 Element IDs: Snt
20256  Time;Comand;n Element IDs: Fer
20257  Time;Command;rep Headcode;nonrep Headcode: Sns-sh Fns-sh
20258  Time;Command;2 Element IDs;Headcode Snt-sh
20259  Time;Command;Location pas
20260  Time;Location Arr Dep
20261  Time;Time;Location Arr & dep together
20262  */
20263 
20264 /*
20265 Perform the starting direction check (Snt & Snt-sh entries). Starts with the train's front element,
20266 checking forwards until it comes to a continuation (no report), a location name that is not null and
20267 different to the train's front element name (whether null or not) (no report), a leading point
20268 (no report) or buffers (report).
20269 */
20270  bool BufferFacingUnReportedFlag = true;
20271  bool TrainFacingBuffersReported = false; //new flag added at v2.19.0 for 'no facing buffers' message instead of BufferFacingUnReportedFlag
20272  for(unsigned int x = 0; x < SingleServiceVector.size(); x++)
20273  {
20274  TTrackElement ThisElement, NextElement;
20275  TTrainDataEntry TDE = SingleServiceVector.at(x);
20276  if(TDE.ActionVector.at(TDE.ActionVector.size() - 1).FormatType == Repeat)
20277  {
20278  SequenceLog += "13a\n";
20279  throw Exception("Repeat entry present in SingleServiceVector at position " + AnsiString(x));
20280  }
20281  const TActionVector &AV = TDE.ActionVector;
20282  if((AV.at(0).Command == "Snt") || (AV.at(0).Command == "Snt-sh"))
20283  {
20284  bool BufferFlag = false;
20285  int FrontTVPos = AV.at(0).FrontStartOrRepeatDigits;
20286  int RearTVPos = AV.at(0).RearStartOrRepeatMins;
20287  AnsiString FrontLocName = AV.at(0).LocationName;
20288  int NextEntryPos, NextExitPos;
20289  ThisElement = Track->TrackElementAt(1395, FrontTVPos);
20290  int ThisExitPos;
20291  if(ThisElement.Conn[0] == RearTVPos)
20292  {
20293  ThisExitPos = 1;
20294  }
20295  else if(ThisElement.Conn[1] == RearTVPos)
20296  {
20297  ThisExitPos = 0;
20298  }
20299  else if(ThisElement.Conn[2] == RearTVPos)
20300  {
20301  ThisExitPos = 3;
20302  }
20303  else if(ThisElement.Conn[3] == RearTVPos)
20304  {
20305  ThisExitPos = 2;
20306  }
20307  if((ThisElement.TrackType == Buffers) && (ThisExitPos == 0))//pos 0 is the buffer
20308  {
20309  BufferFlag = true;
20310  }
20311  else //continue tracking forwards
20312  {
20313  while(true)
20314  {
20315  if(ThisElement.Conn[ThisExitPos] == -1)
20316  {
20317  SequenceLog = "13b\n";
20318  throw Exception("ThisElement connects to -1 for " + TDE.ServiceReference);
20319  }
20320  NextElement = Track->TrackElementAt(1396, ThisElement.Conn[ThisExitPos]);
20321  NextEntryPos = ThisElement.ConnLinkPos[ThisExitPos];
20322  if((NextElement.TrackType == Points) && ((NextEntryPos == 0) || (NextEntryPos == 2))) //leading points
20323  {
20324  BufferFlag = false; //should already be false
20325  break;
20326  }
20327  else if(NextElement.TrackType == Continuation)
20328  {
20329  BufferFlag = false;
20330  break;
20331  }
20332  else if((NextElement.ActiveTrackElementName != "") && (NextElement.ActiveTrackElementName != FrontLocName))
20333  {
20334  BufferFlag = false;
20335  break;
20336  }
20337  else if(NextElement.TrackType == Buffers)
20338  {
20339  BufferFlag = true;
20340  break;
20341  }
20342  else if((NextElement.TrackType == Points) && ((NextEntryPos == 1) || (NextEntryPos == 3))) //trailing points
20343  {
20344  ThisElement = NextElement;
20345  ThisExitPos = 0;
20346  continue;
20347  }
20348  else
20349  {
20350  if(NextEntryPos == 0)
20351  {
20352  NextExitPos = 1;
20353  }
20354  else if(NextEntryPos == 1)
20355  {
20356  NextExitPos = 0;
20357  }
20358  else if(NextEntryPos == 2)
20359  {
20360  NextExitPos = 3;
20361  }
20362  else if(NextEntryPos == 3)
20363  {
20364  NextExitPos = 2;
20365  }
20366  }
20367  ThisElement = NextElement;
20368  ThisExitPos = NextExitPos;
20369  }
20370  }
20371  if(BufferFlag)
20372  {
20373  if(BufferFacingUnReportedFlag)
20374  {
20375  TTFile3 << "Train facing direction on creation analysis:-\n\n";
20376  BufferFacingUnReportedFlag = false;
20377  }
20378  if(AV.at(1).Command != "cdt") //added at v2.19.0
20379  {
20380  TTFile3 << "Service " + TDE.ServiceReference + " facing buffers on creation with no immediate change of direction\n";
20381  TrainFacingBuffersReported = true; //added at v2.19.0
20382  }
20383  }
20384  }
20385  }
20386  if(!TrainFacingBuffersReported) //added at v2.19.0
20387  {
20388  TTFile3 << "Nothing to report for train facing directions\n\n";
20389  }
20390  else
20391  {
20392  TTFile3 << '\n';
20393  }
20394  SequenceLog += "13c\n";
20395 
20396  //Perform the missing cdt check. Check every entry similar to the check in SecondPassActions and if find any print out the full sequence and service entries
20397  AnsiString LocationNameToBeChecked = "";
20398  bool MissingcdtUnreportedFlag = true;
20399  TNumList MarkerList;
20400  for(unsigned int x = 0; x < SingleServiceVector.size(); x++)
20401  {
20402  const TTrainDataEntry &TDEntry = SingleServiceVector.at(x);
20403  unsigned int y = 0;
20404  int FirstInstance = 9999, SecondInstance = 9999; //9999 ensures won't be marked if not changed
20405  bool FullBreak = false;
20406  MarkerList.clear();
20407  // first discard unlocated Snt entries as they don't have location name set
20408  while((y < TDEntry.ActionVector.size()) && !FullBreak)
20409  // need to check each location name separately in turn, skipped for SignallerControl entries
20410  {
20411  if(/*(TDEntry.ActionVector.at(y).Command == "Fer") || */(TDEntry.ActionVector.at(y).FormatType == Repeat) ||
20412  (TDEntry.ActionVector.at(y).Command == "Fjo") || (TDEntry.ActionVector.at(y).Command == "Frh") ||
20413  (TDEntry.ActionVector.at(y).Command == "Frh-sh"))
20414  {
20415  break; // out of the 'while' loop since have reached the end
20416  }
20417  LocationNameToBeChecked = ""; //this section where continuation name assigned for unlocated Snts added at v2.18.0
20418  if((TDEntry.ActionVector.at(y).Command == "Snt") && (TDEntry.ActionVector.at(y).LocationType == EnRoute)) //unlocated Snt - check if the continuation has a name
20419  {
20420  int EntryPos = TDEntry.ActionVector.at(0).RearStartOrRepeatMins; //this is a track vector position
20421  LocationNameToBeChecked = Track->TrackElementAt(1678, EntryPos).ActiveTrackElementName;
20422  }
20423  if(LocationNameToBeChecked == "")
20424  {
20425  if(y == 0) //unlocated and un-named Snt, so skip this value of y
20426  {
20427  y++;
20428  continue;
20429  }
20430  LocationNameToBeChecked = TDEntry.ActionVector.at(y).LocationName; //the only un-named values for ActionVectorEntry::LocationName
20431  } //are for unlocated Snts and Fers
20432  FirstInstance = y;
20433  for(unsigned int z = y; z < TDEntry.ActionVector.size(); z++)
20434  {
20435  const TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(z);
20436  if(/*(AVEntry.Command == "Fer") || */(AVEntry.FormatType == Repeat) ||
20437  (AVEntry.Command == "Fjo") || (AVEntry.Command == "Frh") ||
20438  (AVEntry.Command == "Frh-sh"))
20439  {
20440  break; // out of the 'z' loop since have reached the end & 'Fer' & 'Repeat' have no location name set
20441  }
20442  if(AVEntry.Command == "cdt")
20443  {
20444  break; // out of the 'z' loop since the check is only valid up to a change of direction
20445  }
20446  if(AVEntry.LocationName == LocationNameToBeChecked)
20447  {
20448  continue; // keep going while name same
20449  }
20450  if(AVEntry.LocationName != LocationNameToBeChecked)
20451  // if name different check forwards to see if repeats
20452  {
20453  for(unsigned int a = z; a < TDEntry.ActionVector.size(); a++)
20454  {
20455  AnsiString LocationName;
20456  if(TDEntry.ActionVector.at(a).Command == "cdt")
20457  {
20458  break; // out of the 'a' & 'z' loops since the check is only valid up to a change of direction
20459  }
20460  if(TDEntry.ActionVector.at(a).Command == "Fer") //this section where continuation name assigned for Fers added at v2.18.0
20461  {
20462  int ExitLoc = TDEntry.ActionVector.at(a).ExitList.front();
20463  LocationName = Track->TrackElementAt(1679, ExitLoc).ActiveTrackElementName;
20464  }
20465  else
20466  {
20467  LocationName = TDEntry.ActionVector.at(a).LocationName;
20468  }
20469  if(LocationName == LocationNameToBeChecked)
20470  {
20471  SecondInstance = a;
20472  AnsiString Sequence = TDEntry.ServiceReference;
20473  for(unsigned int b = 0; b < TDEntry.ActionVector.size(); b++)
20474  {
20475  if(TDEntry.ActionVector.at(b).Command.SubString(1,3) == "chr")
20476  {
20477  Sequence = Sequence + AnsiString(" -> ") + TDEntry.ActionVector.at(b).OtherHeadCode;
20478  }
20479  }
20480  if(MissingcdtUnreportedFlag)
20481  {
20482  TTFile3 << "Possibly missing changes of direction - these will be missing unless the service travels in a loop back to the locations marked:-\n\n";
20483  }
20484  TTFile3 << LocationNameToBeChecked << " is listed twice with no direction change between in service sequence: " << Sequence << "\n\n";
20485  MarkerList.push_back(FirstInstance);
20486  MarkerList.push_back(SecondInstance);
20487  SingleServiceOutput(0, x, MarkerList, SingleServiceVector, TTFile3);
20488  MissingcdtUnreportedFlag = false;
20489  FullBreak = true; //no more checks for this sequence
20490  break; //out of the a & z loops
20491  }
20492  }
20493  break; // out of the 'z' loop since have checked 'a' as far as need to
20494  }
20495  }
20496  y++;
20497  }
20498  }
20499  if(MissingcdtUnreportedFlag)
20500  {
20501  TTFile3 << "Nothing to report for missing changes of direction\n\n";
20502  }
20503  else
20504  {
20505  TTFile3 << '\n';
20506  }
20507  SequenceLog += "14\n";
20508 
20509 /* Perform the questionable cdt check. Examine each service in the SingleServiceVector, and if don't find the same
20510  name either side of a cdt (before the next cdt) then flag as a questionable cdt.
20511  Method: Have an outer loop for each service that looks for cdts. When found work backwards to the last cdt or beginning and std::list all the
20512  locations excluding the cdt location. Then work forwards to the next cdt or the end and do the same. Sort each list and make unique (duplicated
20513  names on one side of a cdt already checked either by the tt validator or the missing cdt check). Then compare the two lists and if any location
20514  included in both then ok, else report as questionable. If one list is empty then it is reported.
20515 */
20516  typedef std::list<AnsiString> TLocList;
20517  TLocList BackwardList, ForwardList;
20518  bool IntroLineNeeded = true;
20519  for(unsigned int x = 0; x < SingleServiceVector.size(); x++)
20520  {
20521  unsigned int cdtPosition = 9999;
20522  AnsiString cdtLocation = "";
20523  bool FoundSameName = false;
20524  bool FerEntry = false;
20525  MarkerList.clear();
20526  const TTrainDataEntry &TDEntry = SingleServiceVector.at(x);
20527  for(unsigned int y = 0; y <= TDEntry.ActionVector.size(); y++) // <= because need to examine Fer endings after reached end of vector
20528  // need to check each location name separately in turn, skipped for SignallerControl entries
20529  {
20530  if((y == TDEntry.ActionVector.size()) && !FerEntry)
20531  {
20532  break;
20533  }
20534  BackwardList.clear();
20535  ForwardList.clear();
20536  bool ValidEnd = false;
20537  if(y < TDEntry.ActionVector.size())
20538  {
20539  const TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(y);
20540  if((AVEntry.FormatType == Repeat) || //end of SSVector, shouldn't be any repeats
20541  (AVEntry.Command == "Fjo") || (AVEntry.Command == "Frh") ||
20542  (AVEntry.Command == "Frh-sh"))
20543  {
20544  ValidEnd = true;
20545  }
20546  }
20547  if(FerEntry || ValidEnd)
20548  {
20549  if(MarkerList.empty())
20550  {
20551  break; // out of the 'y' loop since have reached the end & nothing to report
20552  }
20553  else
20554  {
20555  AnsiString Sequence = TDEntry.ServiceReference;
20556  for(unsigned int b = 0; b < TDEntry.ActionVector.size(); b++)
20557  {
20558  if(TDEntry.ActionVector.at(b).Command.SubString(1,3) == "chr")
20559  {
20560  Sequence = Sequence + AnsiString(" -> ") + TDEntry.ActionVector.at(b).OtherHeadCode;
20561  }
20562  }
20563  MarkerList.sort();
20564  if(IntroLineNeeded)
20565  {
20566  TTFile3 << "Questionable change of direction analysis.\n\n";
20567  TTFile3 << "For marked changes of direction there are no same-name locations listed both above (up to the start or another direction change)\n";
20568  TTFile3 << "and below (down to the end or another direction change) but not counting the change of direction location itself.\n\n";
20569  TTFile3 << "These changes of direction are probably valid for movements to and from depots but all should be checked to\n";
20570  TTFile3 << "make sure that none has been included incorrectly:\n\n";
20571  IntroLineNeeded = false;
20572  }
20573  TTFile3 << "Service sequence " << Sequence << " contains questionable changes of direction:-\n\n";
20574  SingleServiceOutput(1, x, MarkerList, SingleServiceVector, TTFile3);
20575  break;
20576  }
20577  }
20578  if(y < TDEntry.ActionVector.size()) //if it is == ...size() then shouldn't have reached here
20579  {
20580  const TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(y);
20581  if(AVEntry.Command != "cdt")
20582  {
20583  continue; //only looking for cdts
20584  }
20585  //here have found a cdt
20586  cdtPosition = y;
20587  cdtLocation = AVEntry.LocationName;
20588  for(int z = y - 1; z >= 0; z--)
20589  {
20590  const TActionVectorEntry &AVEntry2 = TDEntry.ActionVector.at(z);
20591  if(AVEntry2.Command == "cdt")
20592  {
20593  break; //don't look further back than the last cdt
20594  }
20595  AnsiString LocName = ""; //this section where continuation name assigned for unlocated Snts added at v2.18.0
20596  if((AVEntry2.Command == "Snt") && (AVEntry2.LocationType == EnRoute)) //unlocated Snt - check if the continuation has a name
20597  {
20598  int EntryPos = AVEntry2.RearStartOrRepeatMins; //this is a track vector position
20600  }
20601  if(LocName == "")
20602  {
20603  LocName = AVEntry2.LocationName; //the only un-named values for ActionVectorEntry::LocationName
20604  } //are for unlocated Snts and Fers
20605  if((LocName != "") && (AVEntry2.LocationName != cdtLocation)) //if an earlier entry == cdtLocation will have been picked up in an earlier check
20606  {
20607  BackwardList.push_back(LocName);
20608  }
20609  }
20610  BackwardList.sort();
20611  BackwardList.unique();
20612  for(unsigned int z = y + 1; z < TDEntry.ActionVector.size(); z++)
20613  {
20614  const TActionVectorEntry &AVEntry3 = TDEntry.ActionVector.at(z);
20615  if(/*(AVEntry3.Command == "Fer") || */(AVEntry3.FormatType == Repeat) ||
20616  (AVEntry3.Command == "Fjo") || (AVEntry3.Command == "Frh") ||
20617  (AVEntry3.Command == "Frh-sh") || (AVEntry3.Command == "cdt"))
20618  {
20619  break; // out of the 'z' loop since have reached another cdt or the end
20620  }
20621  AnsiString LocName = "";
20622  if(AVEntry3.Command == "Fer") //this section where continuation name assigned for Fers added at v2.18.0
20623  {
20624  int ExitLoc = AVEntry3.ExitList.front();
20625  LocName = Track->TrackElementAt(1681, ExitLoc).ActiveTrackElementName;
20626  FerEntry = true;
20627  }
20628  else
20629  {
20630  LocName = AVEntry3.LocationName;
20631  }
20632  if((LocName != "") && (AVEntry3.LocationName != cdtLocation)) //if a later entry == cdtLocation will have been picked up in an earlier check
20633  {
20634  ForwardList.push_back(LocName);
20635  }
20636  }
20637  ForwardList.sort();
20638  ForwardList.unique();
20639  FoundSameName = false;
20640  //now have both lists compiled (may be empty) so check for same name in both & report if don't find any
20641  if(!BackwardList.empty() && !ForwardList.empty())
20642  {
20643  for(TLocList::iterator BLIt = BackwardList.begin(); BLIt != BackwardList.end(); BLIt++)
20644  {
20645  for(TLocList::iterator FLIt = ForwardList.begin(); FLIt != ForwardList.end(); FLIt++)
20646  {
20647  if(*BLIt == *FLIt)
20648  {
20649  FoundSameName = true;
20650  }
20651  }
20652  }
20653  }
20654  if(!FoundSameName) //report the inability to find same name
20655  {
20656  MarkerList.push_back(cdtPosition);
20657  }
20658  }
20659  }
20660  }
20661  if(IntroLineNeeded)
20662  {
20663  TTFile3 << "Nothing to report for questionable changes of direction\n\n";
20664  }
20665  else
20666  {
20667  TTFile3 << '\n';
20668  }
20669 /*
20670 //debug section
20671 //print all SSVector for diagnostic purposes
20672  TTFile3 << "Whole SSVector\n\n";
20673  TNumList EmptyList;
20674  for(unsigned int x = 0; x < SingleServiceVector.size(); x++)
20675  {
20676  SingleServiceOutput(, x, EmptyList, SingleServiceVector, TTFile3);
20677  }
20678 //end of debug section
20679 */
20680  }
20681  SequenceLog += "15\n";
20682  TTFile3.close();
20683  Utilities->CallLogPop(2212);
20684  return(true);
20685  }
20686 
20687  catch(const Exception &e) //non error catch
20688  {
20689  AnsiString TTErrorFileName = "Analysis Error.txt";
20690  TTErrorFileName = CurDir + "\\Formatted timetables\\" + TTErrorFileName;
20691  std::ofstream TTError(TTErrorFileName.c_str());
20692  if(TTError == 0)
20693  {
20694  ShowMessage("Analysis error file failed to open - can't be created");
20695  Utilities->CallLogPop(2233);
20696  return(false);
20697  }
20698  AnsiString TimeNow = TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss");
20699  TTError << TimeNow.c_str() << '\n' << ArrRange << '\n' << ArrChecked << '\n' << DepRange << '\n' <<
20700  DepChecked << '\n' << AtLocChecked << '\n' << SequenceLog << '\n' << AnsiString(e.Message);
20701 
20702  TTError.close();
20703  ShowMessage("Error in Conflict Analysis: A file called 'Analysis Error.txt' has been created in your Formatted timetables folder. Please send this file together with your railway and timetable files to railwayfeedback@gmail.com for investigation - many thanks");
20704  Utilities->CallLogPop(2226);
20705  return(false);
20706  }
20707 }
20708 
20709 // ---------------------------------------------------------------------------
20710 void TTrainController::SingleServiceOutput(int Caller, int SSVectorNumber, TNumList MarkerList, TTrainDataVector &SingleServiceVector, std::ofstream &VecFile)
20711 {
20712  Utilities->CallLog.push_back(Utilities->TimeStamp() + AnsiString(SSVectorNumber) + ',' + ",SingleServiceOutput");
20713  if((SSVectorNumber < 0) || ((unsigned int)SSVectorNumber >= SingleServiceVector.size()))
20714  {
20715  throw Exception("SSVectorNumber out of range, = " + AnsiString(SSVectorNumber) + ", Vector size = " + SingleServiceVector.size());
20716  }
20717  TTrainDataEntry SingleService = SingleServiceVector.at(SSVectorNumber);
20718  {
20719  VecFile << ",Initial service reference " << SingleService.ServiceReference + '\n';
20720  AnsiString Marker = "";
20721  for(unsigned int x = 0; x < SingleService.ActionVector.size(); x++)
20722  {
20723  Marker = ',';
20724  for(TNumListIterator MLIt = MarkerList.begin(); MLIt != MarkerList.end(); MLIt++)
20725  {
20726  if(int(x) == *MLIt)
20727  {
20728  Marker = "-->,";
20729  break;
20730  }
20731  }
20732  TActionVectorEntry AVE = SingleService.ActionVector.at(x);
20733  if(AVE.FormatType == StartNew)
20734  {
20735  AnsiString RearID = Track->TrackElementAt(1397, AVE.RearStartOrRepeatMins).ElementID;
20736  AnsiString FrontID = Track->TrackElementAt(1398, AVE.FrontStartOrRepeatDigits).ElementID;
20737  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << RearID << ' ' << FrontID << '\n';
20738  }
20739  if(AVE.FormatType == SNTShuttle)
20740  {
20741  AnsiString RearID = Track->TrackElementAt(1399, AVE.RearStartOrRepeatMins).ElementID;
20742  AnsiString FrontID = Track->TrackElementAt(1400, AVE.FrontStartOrRepeatDigits).ElementID;
20743  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << RearID << ' ' << FrontID << ' ' << AVE.OtherHeadCode << '\n';
20744  }
20745  if(AVE.FormatType == SNSShuttle) //should all have been converted to chr
20746  {
20747  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << AVE.OtherHeadCode << ' ' << AVE.NonRepeatingShuttleLinkHeadCode << '\n';
20748  }
20749  if(AVE.FormatType == Repeat) //shouldn't be any repeats, only here to show if any have been copied
20750  {
20751  VecFile << Marker << "Repeat " << AVE.RearStartOrRepeatMins << ' ' << AVE.FrontStartOrRepeatDigits << ' ' << AVE.NumberOfRepeats << '\n';
20752  }
20753  if((AVE.FormatType == TimeCmd) || (AVE.FormatType == TimeCmdHeadCode) || (AVE.FormatType == FNSNonRepeatToShuttle) || (AVE.FormatType == FSHNewService))
20754  {
20755  TActionVectorEntry AVHolder = AVE;
20756  if(AVE.Command.SubString(1,3) == "chr")
20757  {
20758  if(AVE.Command.SubString(5, AVE.Command.Length() - 4) == "sp")
20759  {
20760  AVE.Command = "Change of service to " + AVE.OtherHeadCode + " after split";
20761  AVE.OtherHeadCode = "";
20762  }
20763  if(AVE.Command.SubString(5, AVE.Command.Length() - 4) == "Fns")
20764  {
20765  AVE.Command = "Change of service to ";
20766  }
20767  if(AVE.Command.SubString(5, AVE.Command.Length() - 4) == "Fns-sh")
20768  {
20769  AVE.Command = "Change to shuttle finishing service";
20770  }
20771  if(AVE.Command.SubString(5, AVE.Command.Length() - 4) == "F-nshs")
20772  {
20773  AVE.Command = "Change to shuttle service " + AVE.OtherHeadCode + " from feeder";
20774  AVE.OtherHeadCode = "";
20775  }
20776  }
20777  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << AVE.OtherHeadCode << '\n';
20778  AVE = AVHolder;
20779  }
20780  else if((AVE.FormatType == TimeLoc) && (AVE.ArrivalTime != TDateTime(-1)))
20781  {
20782  VecFile << Marker << Utilities->Format96HHMM(AVE.ArrivalTime) << " Arr " << AVE.LocationName << '\n';
20783  }
20784  else if((AVE.FormatType == TimeLoc) && (AVE.DepartureTime != TDateTime(-1)))
20785  {
20786  VecFile << Marker << Utilities->Format96HHMM(AVE.DepartureTime) << " Dep " << AVE.LocationName << '\n';
20787  }
20788  else if(AVE.FormatType == TimeTimeLoc)
20789  {
20790  VecFile << Marker << Utilities->Format96HHMM(AVE.ArrivalTime) << ' ' << Utilities->Format96HHMM(AVE.DepartureTime) << ' ' << AVE.LocationName << '\n';
20791  }
20792  else if(AVE.FormatType == PassTime)
20793  {
20794  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << ' ' << "Pass" << ' ' << AVE.LocationName << '\n';
20795  }
20796  else if(AVE.FormatType == ExitRailway) //ListOfExits added at v2.10.0
20797  {
20798  AnsiString ListOfExits = "";
20799  for(TNumListIterator NLIt = AVE.ExitList.begin(); NLIt != AVE.ExitList.end(); NLIt++)
20800  {
20801  ListOfExits += AnsiString(Track->TrackElementAt(1432, *NLIt).ElementID) + ' ';
20802  }
20803  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << " Fer " << ListOfExits <<'\n';
20804  }
20805  else if(AVE.FormatType == FinRemHere)
20806  {
20807  VecFile << Marker << "Frh" << '\n';
20808  }
20809  }
20810  VecFile << '\n';
20811  }
20812  Utilities->CallLogPop(2318);
20813 }
20814 
20815 // ---------------------------------------------------------------------------
20816 
20817 TTrainDataEntry TTrainController::GetServiceFromVector(AnsiString Caller, AnsiString HeadCode, TTrainDataVector Vector, bool &FinishType, bool &FoundFlag)
20818 {
20819  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetServiceFromVector," + HeadCode);
20820  FoundFlag = false;
20821  FinishType = true;
20822  for(unsigned int x = 0; x < Vector.size(); x++)
20823  {
20824 // AnsiString ThisRef = Vector.at(x).ServiceReference;
20825  if(Vector.at(x).ServiceReference == HeadCode)
20826  {
20827  if(Vector.at(x).ActionVector.at(Vector.at(x).ActionVector.size() - 1).FormatType == Repeat) //shouldn't be any repeats
20828  {
20829  TActionVectorEntry AVE = Vector.at(x).ActionVector.at(Vector.at(x).ActionVector.size() - 2);
20830  if((AVE.Command == "Fjo") || (AVE.Command == "Frh") || (AVE.Command == "Fer") || (AVE.Command == "Frh-sh"))
20831  {
20832  FinishType = false;
20833  }
20834  }
20835  else
20836  {
20837  TActionVectorEntry AVE = Vector.at(x).ActionVector.at(Vector.at(x).ActionVector.size() - 1);
20838  if((AVE.Command == "Fjo") || (AVE.Command == "Frh") || (AVE.Command == "Fer") || (AVE.Command == "Frh-sh"))
20839  {
20840  FinishType = false;
20841  }
20842  }
20843  FoundFlag = true;
20844  Utilities->CallLogPop(2319);
20845  return(Vector.at(x));
20846  }
20847  }
20848  Utilities->CallLogPop(2320);
20849  return(Vector.at(Vector.size() - 1)); //return last for want of returning something
20850 }
20851 
20852 // ---------------------------------------------------------------------------
20853 
20854 bool TTrainController::WithinTimeRange(int Caller, AnsiString Time1, AnsiString Time2, int MinuteRange) //times are "HH:MM"
20855 {
20856 //convert times to integer minutes
20857  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",WithinTimeRange," + Time1 + "," + Time2 + "," + AnsiString(MinuteRange));
20858  if((Time1 == "") || (Time2 == ""))
20859  {
20860  Utilities->CallLogPop(2213);
20861  return(false);
20862  }
20863  int Mins = Time1.SubString(4,2).ToInt();
20864  int Hours = Time1.SubString(1,2).ToInt();
20865  int Time1Mins = (Hours * 60) + Mins;
20866  Mins = Time2.SubString(4,2).ToInt();
20867  Hours = Time2.SubString(1,2).ToInt();
20868  int Time2Mins = (Hours * 60) + Mins;
20869  if(abs(Time1Mins - Time2Mins) <= MinuteRange)
20870  {
20871  Utilities->CallLogPop(2214);
20872  return(true);
20873  }
20874  Utilities->CallLogPop(2215);
20875  return(false);
20876 }
20877 
20878 // ---------------------------------------------------------------------------
20879 
20880 AnsiString TTrainController::ConsolidateSARNTArrDep(int Caller, const AnsiString Input, int &NumTrainsAtLoc, AnsiString Location, bool Arrival,
20881  bool &AnalysisError, int &MaxNumberOfSameDirections)
20882 {
20883  //input consists of services and service Arr or Dep times as a comma separated list, Location needed to determine direction information
20884 
20885  try
20886  {
20887  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ConsolidateSARNTArrDep," + Input);
20888  AnsiString Output = "", OneService = "", TempStr1 = "", TempStr2 = "";
20889  int SCPos = 0;
20890  std::list<AnsiString> ServiceList; //this is the list of services with times extracted from Input - not to be confused with ServiceCallingPointsList
20891  //first change every second comma in Input to a semicolon so can separate services but keep times with services
20892  bool EvenComma = false;
20893  for(int x = 1; x <= Input.Length(); x++)
20894  {
20895  TempStr1 = Input[x];
20896  if(TempStr1 == AnsiString(',') && EvenComma)
20897  {
20898  TempStr2 += ';';
20899  }
20900  else
20901  {
20902  TempStr2 += Input[x];
20903  }
20904  if(TempStr1 == AnsiString(','))
20905  {
20906  EvenComma = !EvenComma;
20907  }
20908  }
20909  //load up the list of services with associated times
20910  while(TempStr2.Length() > 0)
20911  {
20912  SCPos = TempStr2.Pos(';');
20913  if(SCPos > 0) //0 if not found, as won't be when only one service left
20914  {
20915  OneService = TempStr2.SubString(1, SCPos - 1);
20916  ServiceList.push_back(OneService);
20917  TempStr2 = TempStr2.SubString(SCPos + 1, TempStr2.Length() - SCPos);
20918  }
20919  else //no semicolon so looking at last (or only) element
20920  {
20921  ServiceList.push_back(TempStr2);
20922  TempStr2 = "";
20923  }
20924  }
20925  ServiceList.sort(); // alphabetical order
20926  ServiceList.unique(); //remove duplicates
20927  NumTrainsAtLoc = ServiceList.size(); //calc this after removing duplicates as may not have changed
20928 
20929  //now add direction information from AllServiceCallingLocsMap - key is service ref and value a list of calling points in order
20930  int DirectionMarker = 0; //this is added in & runs from 1 upwards, same marker for diff services = same direction
20931  //first add the direction marker "&0" for not yet allocated - '&' is an identifier
20932  std::list<AnsiString>::iterator SLIt, SLIt1, SLIt2, SLIt3;
20933 
20934  for(SLIt = ServiceList.begin(); SLIt != ServiceList.end(); SLIt++)
20935  {
20936  *SLIt = *SLIt + "&0"; //add in a basic direction marker to each service
20937  }
20938  SLIt3 = ServiceList.end();
20939  SLIt3--; //so points to last element
20940  AnsiString ServiceRef1, ServiceRef2, AnsiTime1, AnsiTime2, RepeatInfo1, RepeatInfo2; //1 refers to first for..next loop & 2 to second
20941  int AmpersandPos, SpacePos, CommaPos1, CommaPos2, RepeatNum1, RepeatNum2;
20942  TAllServiceCallingLocsMap::iterator ASCLIt1, ASCLIt2;
20943  TServiceCallingLocsList ServiceCallingLocsList1, ServiceCallingLocsList2;
20944  MaxNumberOfSameDirections = 0; //at end of each SLIt loop if SameDirectionCount > MaxNumberOfSameDirections then MaxNumberOfSameDirections = SameDirectionCount
20945  int SameDirectionCount = 0; //starts at 1 at each SLIt loop (because SLIt1 entry already has a DirectionMarker) and increments for every same direction
20946 
20947  for(std::list<AnsiString>::iterator SLIt1 = ServiceList.begin(); SLIt1 != SLIt3; SLIt1++) //should be end() - 1 but can't use -1 with lists so have to improvise
20948  {
20949  SLIt = SLIt1;
20950  SLIt++; //so points to one after SLIt1
20951  if(SLIt1->SubString(SLIt1->Length() - 1, 2) != AnsiString("&0"))
20952  {
20953  continue; //already allocated so skip to the next
20954  }
20955  else
20956  {
20957  CommaPos1 = SLIt1->Pos(','); //can't be 0
20958  ServiceRef1 = SLIt1->SubString(1, CommaPos1 - 1);
20959  //but this contains "(First service..." etc so need to strip these, but use to extract RepeatNum
20960  SpacePos = ServiceRef1.Pos(' ');
20961  RepeatNum1 = 0;
20962  if(SpacePos > 0) //otherwise it's already correct
20963  {
20964  RepeatInfo1 = ServiceRef1.SubString(SpacePos + 2, ServiceRef1.Length() - SpacePos - 2); //drops the brackets and leaves "First service", "Repeat 2" etc
20965  ServiceRef1 = ServiceRef1.SubString(1, SpacePos - 1);
20966  if(RepeatInfo1[1] == 'F')
20967  {
20968  RepeatNum1 = 0;
20969  }
20970  else
20971  {
20972  SpacePos = RepeatInfo1.Pos(' ');
20973  RepeatNum1 = RepeatInfo1.SubString(SpacePos + 1, RepeatInfo1.Length() - SpacePos).ToInt();
20974  }
20975  }
20976  AnsiTime1 = SLIt1->SubString(CommaPos1 + 1, SLIt1->Length() - CommaPos1);
20977  //but this includes the "&0" etc so need to strip these
20978  AmpersandPos = AnsiTime1.Pos('&');
20979  AnsiTime1 = AnsiTime1.SubString(1, AmpersandPos - 1);
20980 
20981  ASCLIt1 = AllServiceCallingLocsMap.find(ServiceRef1);
20982  if(ASCLIt1 == AllServiceCallingLocsMap.end()) //can't find it
20983  {
20984  throw Exception("ASCLIt1 Error in " + Input);
20985  }
20986  ServiceCallingLocsList1 = ASCLIt1->second;
20987  AmpersandPos = SLIt1->Pos('&');
20988  *SLIt1 = SLIt1->SubString(1, AmpersandPos); //truncate up to & leave in the '&'
20989  *SLIt1 = *SLIt1 + AnsiString(++DirectionMarker); //now add the next marker (pre-increment), allow for it being more than one digit
20990 
20991  SameDirectionCount = 1;
20992  for(SLIt2 = SLIt; SLIt2 != ServiceList.end(); SLIt2++)
20993  {
20994  CommaPos2 = SLIt2->Pos(','); //can't be 0
20995  ServiceRef2 = SLIt2->SubString(1, CommaPos2 - 1);
20996  //but this contains "(First service..." etc so need to strip these
20997  SpacePos = ServiceRef2.Pos(' ');
20998  RepeatNum2 = 0;
20999  if(SpacePos > 0) //otherwise it's already correct
21000  {
21001  RepeatInfo2 = ServiceRef2.SubString(SpacePos + 2, ServiceRef2.Length() - SpacePos - 2); //drops the brackets and leaves "First service", "Repeat 2" etc
21002  ServiceRef2 = ServiceRef2.SubString(1, SpacePos - 1);
21003  if(RepeatInfo2[1] == 'F')
21004  {
21005  RepeatNum2 = 0;
21006  }
21007  else
21008  {
21009  SpacePos = RepeatInfo2.Pos(' ');
21010  RepeatNum2 = RepeatInfo2.SubString(SpacePos + 1, RepeatInfo2.Length() - SpacePos).ToInt();
21011  }
21012  }
21013  AnsiTime2 = SLIt2->SubString(CommaPos2 + 1, SLIt2->Length() - CommaPos2);
21014  //but this includes the "&0" etc so need to strip these
21015  AmpersandPos = AnsiTime2.Pos('&');
21016  AnsiTime2 = AnsiTime2.SubString(1, AmpersandPos - 1);
21017 
21018  ASCLIt2 = AllServiceCallingLocsMap.find(ServiceRef2);
21019  if(ASCLIt2 == AllServiceCallingLocsMap.end()) //can't find it
21020  {
21021  throw Exception("ASCLIt2 Error in " + Input);
21022  }
21023  ServiceCallingLocsList2 = ASCLIt2->second;
21024  //now compare the two
21025  if(SameDirection(0, ServiceRef1, ServiceRef2, AnsiTime1, AnsiTime2, RepeatNum1, RepeatNum2, ServiceCallingLocsList1, ServiceCallingLocsList2, Location, Arrival))
21026  {
21027  int AmpersandPos = SLIt2->Pos('&');
21028  *SLIt2 = SLIt2->SubString(1, AmpersandPos); //truncate up to & leave in the '&'
21029  *SLIt2 = *SLIt2 + AnsiString(DirectionMarker); //now add the same marker as *SLIt1
21030  SameDirectionCount++;
21031  }
21032  }
21033  if(SameDirectionCount > MaxNumberOfSameDirections)
21034  {
21035  MaxNumberOfSameDirections = SameDirectionCount;
21036  }
21037  }
21038  }
21039 
21040  if(SLIt3->SubString(SLIt3->Length() - 1, 2) == AnsiString("&0")) //*SLTIt3 is the last in the list and may not have been allocated, if not it doesn't match
21041  {
21042  //any existing direction so allocate it now
21043  AmpersandPos = SLIt3->Pos('&');
21044  *SLIt3 = SLIt3->SubString(1, AmpersandPos); //truncate up to & leave in the '&'
21045  *SLIt3 = *SLIt3 + AnsiString(++DirectionMarker);
21046  }
21047  //now change direction markers to upper case letters beginning with 'A' (and continuing with 'AA' if exceed 26) & add a comma before so have ServiceRef, DirectionMarker, Time
21048  for(SLIt = ServiceList.begin(); SLIt != ServiceList.end(); SLIt++)
21049  {
21050  //extract the DirectionMarker as an integer
21051  AmpersandPos = SLIt->Pos('&');
21052  AnsiString DirectionMarkerString = SLIt->SubString(AmpersandPos + 1, SLIt->Length() - AmpersandPos); //extract the number as an ansistring
21053  AnsiString ServiceWithoutMarker = SLIt->SubString(1, AmpersandPos - 1); //truncate the &number
21054  DirectionMarker = DirectionMarkerString.ToInt();
21055  AnsiString DirectionSuffix = "";
21056  char c;
21057  if(DirectionMarker < 27)
21058  {
21059  c = 64 + DirectionMarker; //so 1 -> 'A'
21060  DirectionSuffix = "," + AnsiString(c);
21061  }
21062  else if(DirectionMarker < 53)
21063  {
21064  c = 65 + DirectionMarker - 27; //so 27 -> 'AA'
21065  DirectionSuffix = ",A" + AnsiString(c);
21066  }
21067  else
21068  {
21069  DirectionSuffix = ",?"; //shouldn'tn ever get this far!
21070  }
21071  *SLIt = ServiceWithoutMarker + DirectionSuffix;
21072  }
21073  //now prepare the final consolidated output
21074  for(SLIt = ServiceList.begin(); SLIt != ServiceList.end(); SLIt++)
21075  {
21076  Output = Output + *SLIt + ","; //will end up with an unwanted comma at the end
21077  }
21078  if(Output.Length() > 0)
21079  {
21080  Output = Output.SubString(1, Output.Length() - 1); //remove the last comma
21081  }
21082  Utilities->CallLogPop(2216);
21083  return(Output);
21084  }
21085 
21086  catch(const Exception &e) //non error catch
21087  {
21088  AnalysisError = true;
21089  Utilities->CallLogPop(2227);
21090  return(e.Message);
21091  }
21092 }
21093 
21094 // ---------------------------------------------------------------------------
21095 
21096 AnsiString TTrainController::ConsolidateSARNTAtLoc(int Caller, const AnsiString Input, int &NumTrainsAtLoc)
21097 {
21098  //similar to above but doesn't include times in the input
21099  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ConsolidateSARNTAtLoc," + Input);
21100  AnsiString InternalInput = Input, Output = "", OneService = "";
21101  int CommaPos = 0;
21102  std::list<AnsiString> ServiceList;
21103  //load up the list
21104  while(InternalInput.Length() > 0)
21105  {
21106  CommaPos = InternalInput.Pos(',');
21107  if(CommaPos > 0) //0 if not found, as won't be when only one service left
21108  {
21109  OneService = InternalInput.SubString(1, CommaPos - 1);
21110  ServiceList.push_back(OneService);
21111  InternalInput = InternalInput.SubString(CommaPos + 1, InternalInput.Length() - CommaPos);
21112  }
21113  else //no comma so looking at last (or only) element
21114  {
21115  ServiceList.push_back(InternalInput);
21116  InternalInput = "";
21117  }
21118  }
21119 
21120  ServiceList.sort(); // alphabetical order
21121  ServiceList.unique(); //remove duplicates
21122  NumTrainsAtLoc = ServiceList.size(); //calc this after removing duplicates as may not have changed
21123  for(std::list<AnsiString>::iterator SLIt = ServiceList.begin(); SLIt != ServiceList.end(); SLIt++)
21124  {
21125  Output = Output + *SLIt + ","; //will end up with an unwanted comma at the end
21126  }
21127  if(Output.Length() > 0)
21128  {
21129  Output = Output.SubString(1, Output.Length() - 1); //remove the last comma
21130  }
21131  Utilities->CallLogPop(2217);
21132  return(Output);
21133 }
21134 
21135 // ---------------------------------------------------------------------------
21136 
21137 
21138 bool TTrainController::SameDirection(int Caller, AnsiString Ref1In, AnsiString Ref2In, AnsiString Time1, AnsiString Time2, int RepeatNum1, int RepeatNum2, TServiceCallingLocsList List1,
21139  TServiceCallingLocsList List2, AnsiString Location, bool Arrival)
21140 {
21141  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SameDirection," + Ref1In + "," + Ref2In + "," + Time1 + "," + Time2 + "," +
21142  AnsiString(RepeatNum1) + "," + AnsiString(RepeatNum2) + "," + Location);
21143 
21144  std::list<AnsiString>::iterator LP1, LP2, ListPtr1, ListPtr2, LocPtr1, LocPtr2; //LP1 & 2 are temporary pointers, ListPtrs are
21145  //general list pointers, LocPtrs point to Location in the two lists
21146 
21147  //first find the relevant values for LocPtr1 & LocPtr2 taking account of cdts and times
21148  //for List1
21149  bool LocFound = false;
21150  AnsiString Ref1 = Ref1In, Ref2 = Ref2In;
21151  int IncMinutes;
21152  TDateTime FirstServiceTime;
21153 
21154  //first need to strip off /1, /2 etc if present from Ref1 & Ref2 (leave Ref1In & Ref2In for error message & retain value as target in finding the correct reference for cdts)
21155  int Ref1Target = 0, Ref1Count = 0;
21156  int Ref2Target = 0, Ref2Count = 0;
21157 
21158 /* drop this after retained slashes in ServiceRef
21159  int SlashPos = Ref1.Pos('/');
21160  if(SlashPos > 0) //if 0 Ref1 == Ref1In & target stays at 0
21161  {
21162  Ref1Target = Ref1.SubString(SlashPos + 1, Ref1.Length() - SlashPos).ToInt();
21163  Ref1 = Ref1.SubString(1, SlashPos - 1); //truncate up to but omit '/'
21164  }
21165  int Ref2Target = 0, Ref2Count = 0;
21166  SlashPos = Ref2.Pos('/');
21167  if(SlashPos > 0) //if 0 leave as is
21168  {
21169  Ref2Target = Ref2.SubString(SlashPos + 1, Ref2.Length() - SlashPos).ToInt();
21170  Ref2 = Ref2.SubString(1, SlashPos - 1); //truncate up to but omit '/'
21171  }
21172 */
21173 
21174  for(ListPtr1 = List1.begin(); ListPtr1 != List1.end(); ListPtr1++) //note that when this routine entered Ref1In & Ref2In are already set to the correct services,
21175  {
21176  //even if others have same names. But if there are cdt's then need to refind the correct service
21177  if((*ListPtr1) == Location) //
21178  {
21179  LocPtr1 = ListPtr1; //may be modified later
21180  LocFound = true;
21181  }
21182  if(ListPtr1->SubString(1, 3) == "%%%")
21183  {
21184  AnsiString CDTTime = ListPtr1->SubString(4, 5);
21185  //now adjust the time to correspond to the repeat if there is one
21186  if(RepeatNum1 > 0) //if it is 0 then AnsiTime1 is already valid
21187  {
21188  IncMinutes = -1;
21189  FirstServiceTime = TDateTime(-1);
21190  bool BreakFlag = false;
21191  for(TTrainDataVector::iterator TDVIt = TrainDataVectorCopy.begin(); TDVIt != TrainDataVectorCopy.end(); TDVIt++)
21192  {
21193  if(TDVIt->ServiceReference == Ref1)
21194  {
21195  if(Ref1Target > Ref1Count)
21196  {
21197  Ref1Count++;
21198  continue;
21199  }
21200  IncMinutes = TDVIt->ActionVector.back().RearStartOrRepeatMins;
21201  for(TActionVector::iterator AVIt = TDVIt->ActionVector.begin(); AVIt != TDVIt->ActionVector.end(); AVIt++)
21202  {
21203  if(Utilities->Format96HHMM(AVIt->EventTime) == CDTTime)
21204  {
21205  FirstServiceTime = AVIt->EventTime; //i.e. the FirstService value of CDTTime
21206  BreakFlag = true;
21207  break;
21208  }
21209  if(Utilities->Format96HHMM(AVIt->ArrivalTime) == CDTTime) //add arr & dep in case find sooner (though dep shouldn't be sooner)
21210  {
21211  FirstServiceTime = AVIt->ArrivalTime;
21212  BreakFlag = true;
21213  break;
21214  }
21215  if(Utilities->Format96HHMM(AVIt->DepartureTime) == CDTTime)
21216  {
21217  FirstServiceTime = AVIt->DepartureTime;
21218  BreakFlag = true;
21219  break;
21220  }
21221  }
21222  if(BreakFlag)
21223  {
21224  break;
21225  }
21226  }
21227  }
21228  if(IncMinutes == -1)
21229  {
21230  throw Exception("Failed to find service for ServiceRef1 in SameDirection " + Ref1In + " " + Ref2In + " " + Time1 + " " + Time2 + " " + AnsiString(RepeatNum1) + " " + AnsiString(RepeatNum2) + " " + Location);
21231  }
21232  if(FirstServiceTime == TDateTime(-1))
21233  {
21234  throw Exception("Failed to find first service time for ServiceRef1 in SameDirection " + Ref1In + " " + Ref2In + " " + Time1 + " " + Time2 + " " + AnsiString(RepeatNum1) + " " + AnsiString(RepeatNum2) + " " + Location);
21235  }
21236  CDTTime = Utilities->Format96HHMM(TrainController->GetRepeatTime(60, FirstServiceTime, RepeatNum1, IncMinutes));
21237  }
21238  if(!Arrival && (Time1 == CDTTime)) //continue if equal in case next is a departure for the Location
21239  {
21240  LocFound = false;
21241  continue;
21242  }
21243  if(Arrival && (Time1 == CDTTime)) //gone far enough so can stop
21244  {
21245  break;
21246  }
21247  if(Time1 > CDTTime) //not there yet so go on
21248  {
21249  LocFound = false;
21250  continue;
21251  }
21252  if(Time1 < CDTTime) //gone too far so can stop now
21253  {
21254  break;
21255  }
21256  }
21257  }
21258  if(!LocFound) //have to find it in both lists
21259  {
21260  Utilities->CallLogPop(2228);
21261  return( false);
21262  }
21263  //for List2
21264  LocFound = false;
21265  for(ListPtr2 = List2.begin(); ListPtr2 != List2.end(); ListPtr2++)
21266  {
21267  if((*ListPtr2) == Location)
21268  {
21269  LocPtr2 = ListPtr2; //may be modified later
21270  LocFound = true;
21271  }
21272  if(ListPtr2->SubString(1, 3) == "%%%")
21273  {
21274  AnsiString CDTTime = ListPtr2->SubString(4, 5);
21275  //now adjust the time to correspond to the repeat if there is one
21276  if(RepeatNum2 > 0) //if it is 0 then AnsiTime1 is already valid
21277  {
21278  IncMinutes = -1;
21279  FirstServiceTime = TDateTime(-1);
21280  bool BreakFlag = false;
21281  for(TTrainDataVector::iterator TDVIt = TrainDataVectorCopy.begin(); TDVIt != TrainDataVectorCopy.end(); TDVIt++)
21282  {
21283  if(TDVIt->ServiceReference == Ref2)
21284  {
21285  if(Ref2Target > Ref2Count)
21286  {
21287  Ref2Count++;
21288  continue;
21289  }
21290  IncMinutes = TDVIt->ActionVector.back().RearStartOrRepeatMins;
21291  for(TActionVector::iterator AVIt = TDVIt->ActionVector.begin(); AVIt != TDVIt->ActionVector.end(); AVIt++)
21292  {
21293  if(Utilities->Format96HHMM(AVIt->EventTime) == CDTTime)
21294  {
21295  FirstServiceTime = AVIt->EventTime;
21296  BreakFlag = true;
21297  break;
21298  }
21299  if(Utilities->Format96HHMM(AVIt->ArrivalTime) == CDTTime)
21300  {
21301  FirstServiceTime = AVIt->ArrivalTime;
21302  BreakFlag = true;
21303  break;
21304  }
21305  if(Utilities->Format96HHMM(AVIt->DepartureTime) == CDTTime)
21306  {
21307  FirstServiceTime = AVIt->DepartureTime;
21308  BreakFlag = true;
21309  break;
21310  }
21311  }
21312  if(BreakFlag)
21313  {
21314  break;
21315  }
21316  }
21317  }
21318  if(IncMinutes == -1)
21319  {
21320  throw Exception("IncMinutes -1 for ServiceRef2 in SameDirection " + Ref1In + " " + Ref2In + " " + Time1 + " " + Time2 + " " + AnsiString(RepeatNum1) + " " + AnsiString(RepeatNum2) + " " + Location);
21321  }
21322  if(FirstServiceTime == TDateTime(-1))
21323  {
21324  throw Exception("First service time -1 for ServiceRef2 in SameDirection " + Ref1In + " " + Ref2In + " " + Time1 + " " + Time2 + " " + AnsiString(RepeatNum1) + " " + AnsiString(RepeatNum2) + " " + Location);
21325  }
21326  CDTTime = Utilities->Format96HHMM(TrainController->GetRepeatTime(61, FirstServiceTime, RepeatNum2, IncMinutes));
21327  }
21328  if(!Arrival && (Time2 == CDTTime)) //continue if equal in case next is a departure for the Location
21329  {
21330  LocFound = false;
21331  continue;
21332  }
21333  if(Arrival && (Time2 == CDTTime)) //gone far enough so can stop
21334  {
21335  break;
21336  }
21337  if(Time2 > CDTTime) //not there yet so go on
21338  {
21339  LocFound = false;
21340  continue;
21341  }
21342  if(Time2 < CDTTime) //gone too far so can stop now
21343  {
21344  break;
21345  }
21346  }
21347  }
21348  if(!LocFound) //have to find it in both lists, and should be found but allow for it not being
21349  {
21350  Utilities->CallLogPop(2229);
21351  return( false);
21352  }
21353  //now, for the arrival analysis, see if there is a common location before the LocPtrs & within any cdts, and if so return true, else return false
21354  //set ListPtr1 to the search start position
21355  if(Arrival)
21356  {
21357  LP1 = List1.begin();
21358  LP1--; //now points to before the first entry
21359  for(ListPtr1 = LocPtr1; ListPtr1 != LP1; ListPtr1--) //search backwards from Location
21360  {
21361  if(ListPtr1 == List1.begin())
21362  {
21363  break;
21364  }
21365  if(ListPtr1->SubString(1, 3) == "%%%") //a cdt event
21366  {
21367  ListPtr1++; //point to one past the cdt
21368  break;
21369  }
21370  }
21371  //set ListPtr2 to the search start position
21372  LP2 = List2.begin();
21373  LP2--; //now points to before the first entry
21374  for(ListPtr2 = LocPtr2; ListPtr2 != LP2; ListPtr2--)
21375  {
21376  if(ListPtr2 == List2.begin())
21377  {
21378  break;
21379  }
21380  if(ListPtr2->SubString(1, 3) == "%%%") //a cdt event
21381  {
21382  ListPtr2++; //point to one past the cdt
21383  break;
21384  }
21385  }
21386  //ListPtr1 & 2 now at search start position
21387  LP1 = ListPtr1;
21388  LP2 = ListPtr2;
21389  //now search forwards, i.e. for common locations before Location
21390  for(ListPtr1 = LP1; ListPtr1 != List1.end(); ListPtr1++)
21391  {
21392  if(ListPtr1 == LocPtr1) //reached Location without finding a common earlier location so skip to the backwards check
21393  {
21394  break;
21395  }
21396  if(ListPtr1->SubString(1, 3) == "%%%") //reached a cdt event without finding a common earlier location
21397  {
21398  break;
21399  }
21400  for(ListPtr2 = LP2; ListPtr2 != List2.end(); ListPtr2++)
21401  {
21402  if(ListPtr2 == LocPtr2) //not found common earlier location so go to the next ListPtr1
21403  {
21404  break;
21405  }
21406  if(ListPtr2->SubString(1, 3) == "%%%") //reached a cdt event without finding a common earlier location so go to the next ListPtr1
21407  {
21408  break;
21409  }
21410  if((*ListPtr1) == (*ListPtr2)) //found a common earlier location
21411  {
21412  Utilities->CallLogPop(2230);
21413  return( true);
21414  }
21415  }
21416  }
21417  }
21418 
21419  //now, for the departure analysis, reset the start positions and search locations after Location
21420 
21421  else
21422  {
21423  LP1 = LocPtr1;
21424  LP1++; //start at one past the location itself
21425  LP2 = LocPtr2;
21426  LP2++;
21427  for(ListPtr1 = LP1; ListPtr1 != List1.end(); ListPtr1++)
21428  {
21429  if(ListPtr1 == List1.end()) //reached end point so stop
21430  {
21431  break;
21432  }
21433  if(ListPtr1->SubString(1, 3) == "%%%") //reached a cdt event without finding a common location
21434  {
21435  break;
21436  }
21437  for(ListPtr2 = LP2; ListPtr2 != List2.end(); ListPtr2++)
21438  {
21439  if(ListPtr2 == List2.end()) //reached end point so go to next ListPtr1
21440  {
21441  break;
21442  }
21443  if(ListPtr2->SubString(1, 3) == "%%%") //reached a cdt event without finding a common location so go to the next ListPtr1
21444  {
21445  break;
21446  }
21447  if((*ListPtr1) == (*ListPtr2)) //found a common later location
21448  {
21449  Utilities->CallLogPop(2231);
21450  return( true);
21451  }
21452  }
21453  }
21454  }
21455  Utilities->CallLogPop(2232);
21456  return( false);
21457 }
21458 
21459 // ---------------------------------------------------------------------------
21460 
21461 AnsiString TTrainController::GetExitLocationAndAt(int Caller, TNumList &ExitList, AnsiString &AllowedExits) const
21462 {
21463  // changed at v2.7.0 to show allowable exit elements
21464  if(ExitList.empty())
21465  {
21466  return("");
21467  }
21468  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetExitLocationAndAt");
21469  AnsiString StartName = Track->TrackElementAt(735, *(ExitList.begin())).ActiveTrackElementName;
21470  AnsiString ExitLocList = "";
21471  AllowedExits = "";
21472 
21473  unsigned int Counter = 0;
21474  for(TNumListIterator ELIt = ExitList.begin(); ELIt != ExitList.end(); ELIt++)
21475  {
21476  ExitLocList += Track->TrackElementAt(1018, *ELIt).ElementID + " ";
21477  Counter++;
21478  if(((Counter % 6) == 0) && (Counter < (ExitList.size() - 1))) // only add a newline if more to come
21479  {
21480  ExitLocList += "\n";
21481  }
21482  }
21483  if(StartName == "")
21484  {
21485  if(ExitList.size() == 1)
21486  {
21487  AnsiString ID = Track->TrackElementAt(738, *(ExitList.begin())).ElementID;
21488  Utilities->CallLogPop(1571);
21489  return(" at " + ID);
21490  }
21491  else
21492  {
21493  Utilities->CallLogPop(1572);
21494  if(ExitList.size() < 4)
21495  {
21496  AllowedExits = ",\nallowable exit element(s): " + ExitLocList;
21497  return("");
21498  }
21499  else
21500  {
21501  AllowedExits = ",\nallowable exit element(s):\n" + ExitLocList;
21502  return("");
21503  }
21504  }
21505  }
21506  for(TNumListIterator ELIT = ExitList.begin(); ELIT != ExitList.end(); ELIT++)
21507  {
21508  if(Track->TrackElementAt(736, *ELIT).ActiveTrackElementName != StartName)
21509  {
21510  Utilities->CallLogPop(1570);
21511  if(ExitList.size() < 4)
21512  {
21513  AllowedExits = ",\nallowable exit element(s): " + ExitLocList;
21514  return("");
21515  }
21516  else
21517  {
21518  AllowedExits = ",\nallowable exit element(s):\n" + ExitLocList;
21519  return("");
21520  }
21521  }
21522  }
21523  Utilities->CallLogPop(1569);
21524  if(ExitList.size() < 4)
21525  {
21526  AllowedExits = ",\nallowable exit element(s): " + ExitLocList;
21527  return(" at " + StartName);
21528  }
21529  else
21530  {
21531  AllowedExits = ",\nallowable exit element(s):\n" + ExitLocList;
21532  return(" at " + StartName);
21533  }
21534 }
21535 
21536 // ---------------------------------------------------------------------------
21537 /* can't trust this as locations within a vector may not be contiguous
21538  bool TTrainController::IsServiceTerminating(int Caller, TTrainDataEntry *TDEPtr, TActionVectorEntry *AVPtr)
21539  {
21540  //Search ActionVector from the position after the entry value for Ptr to the end, and return true if find a Finish
21541  //entry before Fer or TimeLoc. No point checking for TimeTimeLoc since at a stop location now so a later TimeTimeLoc
21542  //must be preceded by a TimeLoc departure
21543  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsServiceTerminating");
21544  for(unsigned int x=1;x<TDEPtr->ActionVector.size();x++)
21545  {
21546  if((AVPtr + x) < TDEPtr->ActionVector.end())
21547  {
21548  AnsiString xx = (AVPtr + x)->Command;//test
21549  TTimetableFormatType xy = (AVPtr + x)->FormatType;//test
21550  TTimetableSequenceType xz = (AVPtr + x)->SequenceType;//test
21551  if(((AVPtr + x)->Command == "Fer") || ((AVPtr + x)->FormatType == TimeLoc))
21552  {
21553  Utilities->CallLogPop();
21554  return false;
21555  }
21556  else if((AVPtr + x)->SequenceType == FinishSequence)
21557  {
21558  Utilities->CallLogPop();
21559  return true;
21560  }
21561  }
21562  }
21563  Utilities->CallLogPop();
21564  return false;
21565  }
21566 */
21567 // ---------------------------------------------------------------------------
21568 
21569 void TTrainController::SendPerformanceSummary(int Caller, std::ofstream &PerfFile)
21570 {
21571  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SendPerformanceSummary");
21572  AnsiString FormatStr = "####0.0";
21573  AnsiString AvLateArrMins = "";
21574  AnsiString AvEarlyArrMins = "";
21575  AnsiString AvLatePassMins = "";
21576  AnsiString AvEarlyPassMins = "";
21577  AnsiString AvLateDepMins = "";
21578  AnsiString AvLateExitMins = "";
21579  AnsiString AvEarlyExitMins = "";
21580 
21581  //calculate remaining CumulativeDelayedRandMinsAllTrains for trains still in vector (CumulativeDelayedRandMinsAllTrains for exited or removed trains already accounted for)
21582  for(unsigned int x = 0; x < TrainVector.size(); x++)
21583  {
21584  Utilities->CumulativeDelayedRandMinsAllTrains += int(TrainVectorAt(89, x).CumulativeDelayedRandMinsOneTrain);
21585  }
21586 
21587  if(LateArrivals > 0)
21588  {
21589  AvLateArrMins = FormatFloat(FormatStr, (TotLateArrMins / LateArrivals));
21590  }
21591  if(EarlyArrivals > 0)
21592  {
21593  AvEarlyArrMins = FormatFloat(FormatStr, (TotEarlyArrMins / EarlyArrivals));
21594  }
21595  if(LatePasses > 0)
21596  {
21597  AvLatePassMins = FormatFloat(FormatStr, (TotLatePassMins / LatePasses));
21598  }
21599  if(EarlyPasses > 0)
21600  {
21601  AvEarlyPassMins = FormatFloat(FormatStr, (TotEarlyPassMins / EarlyPasses));
21602  }
21603  if(LateDeps > 0)
21604  {
21605  AvLateDepMins = FormatFloat(FormatStr, (TotLateDepMins / LateDeps));
21606  }
21607  if(LateExits > 0) //added at v2.9.1
21608  {
21609  AvLateExitMins = FormatFloat(FormatStr, (TotLateExitMins / LateExits));
21610  }
21611  if(EarlyExits > 0) //added at v2.9.1
21612  {
21613  AvEarlyExitMins = FormatFloat(FormatStr, (TotEarlyExitMins / EarlyExits));
21614  }
21615  PerfFile << '\n' << '\n' << "***************************************";
21616  PerfFile << '\n' << '\n' << "Performance summary:" << '\n';
21617 
21618  if(OnTimeArrivals != 1)
21619  {
21620  PerfFile << OnTimeArrivals << " on-time arrivals" << '\n';
21621  }
21622  else
21623  {
21624  PerfFile << OnTimeArrivals << " on-time arrival" << '\n';
21625  }
21626  if(LateArrivals > 1)
21627  {
21628  PerfFile << LateArrivals << " late arrivals (average " << AvLateArrMins.c_str() << " min)" << '\n';
21629  }
21630  else if(LateArrivals == 1)
21631  {
21632  PerfFile << LateArrivals << " late arrival (" << AvLateArrMins.c_str() << " min)" << '\n';
21633  }
21634  else
21635  {
21636  PerfFile << LateArrivals << " late arrivals" << '\n';
21637  }
21638  if(EarlyArrivals > 1)
21639  {
21640  PerfFile << EarlyArrivals << " early arrivals (average " << AvEarlyArrMins.c_str() << " min)" << '\n';
21641  }
21642  else if(EarlyArrivals == 1)
21643  {
21644  PerfFile << EarlyArrivals << " early arrival (" << AvEarlyArrMins.c_str() << " min)" << '\n';
21645  }
21646  else
21647  {
21648  PerfFile << EarlyArrivals << " early arrivals" << '\n';
21649  }
21650  if(OnTimePasses != 1)
21651  {
21652  PerfFile << OnTimePasses << " on-time passes" << '\n';
21653  }
21654  else
21655  {
21656  PerfFile << OnTimePasses << " on-time pass" << '\n';
21657  }
21658  if(LatePasses > 1)
21659  {
21660  PerfFile << LatePasses << " late passes (average " << AvLatePassMins.c_str() << " min)" << '\n';
21661  }
21662  else if(LatePasses == 1)
21663  {
21664  PerfFile << LatePasses << " late pass (" << AvLatePassMins.c_str() << " min)" << '\n';
21665  }
21666  else
21667  {
21668  PerfFile << LatePasses << " late passes" << '\n';
21669  }
21670  if(EarlyPasses > 1)
21671  {
21672  PerfFile << EarlyPasses << " early passes (average " << AvEarlyPassMins.c_str() << " min)" << '\n';
21673  }
21674  else if(EarlyPasses == 1)
21675  {
21676  PerfFile << EarlyPasses << " early pass (" << AvEarlyPassMins.c_str() << " min)" << '\n';
21677  }
21678  else
21679  {
21680  PerfFile << EarlyPasses << " early passes" << '\n';
21681  }
21682 
21683  if(OnTimeExits != 1) //this batch added at v2.9.1
21684  {
21685  PerfFile << OnTimeExits << " on-time exits" << '\n';
21686  }
21687  else
21688  {
21689  PerfFile << OnTimeExits << " on-time exit" << '\n';
21690  }
21691  if(LateExits > 1)
21692  {
21693  PerfFile << LateExits << " late exits (average " << AvLateExitMins.c_str() << " min)" << '\n';
21694  }
21695  else if(LateExits == 1)
21696  {
21697  PerfFile << LateExits << " late exit (" << AvLateExitMins.c_str() << " min)" << '\n';
21698  }
21699  else
21700  {
21701  PerfFile << LateExits << " late exits" << '\n';
21702  }
21703  if(EarlyExits > 1)
21704  {
21705  PerfFile << EarlyExits << " early exits (average " << AvEarlyExitMins.c_str() << " min)" << '\n';
21706  }
21707  else if(EarlyExits == 1)
21708  {
21709  PerfFile << EarlyExits << " early exit (" << AvEarlyExitMins.c_str() << " min)" << '\n';
21710  }
21711  else
21712  {
21713  PerfFile << EarlyExits << " early exits" << '\n';
21714  }
21715 
21716  if(OnTimeDeps != 1)
21717  {
21718  PerfFile << OnTimeDeps << " on-time departures" << '\n';
21719  }
21720  else
21721  {
21722  PerfFile << OnTimeDeps << " on-time departure" << '\n';
21723  }
21724  if(LateDeps > 1)
21725  {
21726  PerfFile << LateDeps << " late departures (average " << AvLateDepMins.c_str() << " min)" << '\n';
21727  }
21728  else if(LateDeps == 1)
21729  {
21730  PerfFile << LateDeps << " late departure (" << AvLateDepMins.c_str() << " min)" << '\n';
21731  }
21732  else
21733  {
21734  PerfFile << LateDeps << " late departures" << '\n';
21735  }
21736  TDateTime TempExcessLCDownTime;
21737  for(unsigned int x = 0; x < Track->BarriersDownVector.size(); x++) //added at v2.6.0 - should have been added earlier
21738  {
21739 // if(Track->BarriersDownVector.at(x).ReducedTimePenalty) //assume train still to cross LC as probably will, else have false high value & can have
21740  //later perf summaries with lower values, changed at v2.8.0
21741 // {
21742  TempExcessLCDownTime = TrainController->TTClockTime - Track->BarriersDownVector.at(x).StartTime - TDateTime(180.0 / 86400);
21743 // }
21744 /*
21745  else
21746  {
21747  TempExcessLCDownTime = TrainController->TTClockTime - Track->BarriersDownVector.at(x).StartTime;
21748  }
21749 */
21750  if(TempExcessLCDownTime > TDateTime(0))
21751  {
21752  TrainController->ExcessLCDownMins += (double(TempExcessLCDownTime) * 1440);
21753  }
21754  }
21755 
21756  AnsiString FormattedExcessLCDownMins = FormatFloat(FormatStr, ExcessLCDownMins);
21757 
21758  if(ExcessLCDownMins > 0.1)
21759  {
21760  PerfFile << FormattedExcessLCDownMins.c_str() << " excess minutes of level crossing barrier down time" << '\n';
21761  }
21762  else
21763  {
21764  ExcessLCDownMins = 0; //added at v2.16.1 so doesn't count towards performance score if < 0.1mins (else can have low score with no excess mins recorded)
21765  }
21766  if(Utilities->CumulativeDelayedRandMinsAllTrains > 0) //added at v2.13.0
21767  {
21768  PerfFile << Utilities->CumulativeDelayedRandMinsAllTrains << " minutes lost due to random delays when stopped at locations" << '\n';
21769  }
21770  if(MissedStops != 1)
21771  {
21772  PerfFile << MissedStops << " missed stops" << '\n';
21773  }
21774  else
21775  {
21776  PerfFile << MissedStops << " missed stop" << '\n';
21777  }
21778  if(OtherMissedEvents != 1)
21779  {
21780  PerfFile << OtherMissedEvents << " other missed events" << '\n';
21781  }
21782  else
21783  {
21784  PerfFile << OtherMissedEvents << " other missed event" << '\n';
21785  }
21786  if(SkippedTTEvents != 1)
21787  {
21788  PerfFile << SkippedTTEvents << " skipped timetable events" << '\n';
21789  }
21790  else
21791  {
21792  PerfFile << SkippedTTEvents << " skipped timetable event" << '\n';
21793  }
21794  if(UnexpectedExits != 1)
21795  {
21796  PerfFile << UnexpectedExits << " unexpected train exits" << '\n';
21797  }
21798  else
21799  {
21800  PerfFile << UnexpectedExits << " unexpected train exit" << '\n';
21801  }
21802  if(IncorrectExits != 1)
21803  {
21804  PerfFile << IncorrectExits << " incorrect train exits" << '\n';
21805  }
21806  else
21807  {
21808  PerfFile << IncorrectExits << " incorrect train exit" << '\n';
21809  }
21810  if(NumFailures != 1)
21811  {
21812  PerfFile << NumFailures << " train failures" << '\n';
21813  }
21814  else
21815  {
21816  PerfFile << NumFailures << " train failure" << '\n';
21817  }
21818  if(AvHoursIntValue > 0)
21819  {
21820  if(AvHoursIntValue == 1)
21821  {
21822  PerfFile << AvHoursIntValue << " hour mean time betweeen train failures" << '\n';
21823  }
21824  else
21825  {
21826  PerfFile << AvHoursIntValue << " hours mean time betweeen train failures" << '\n';
21827  }
21828  }
21829  AnsiString AvLateMinsLocsNotReached = "";
21830 
21832  int LocsNotReached = (NotStartedTrainLateArr + OperatingTrainLateArr); //dropped divide by 2 after 2.7.0 as don't count late departures as 'failed to arrive'
21833  // each location has an arrival and departure (generally) so divide by 2 - no, dropped after 2.7.0
21834 
21835  if(LocsNotReached > 0)
21836  {
21837  AvLateMinsLocsNotReached = FormatFloat(FormatStr, (OperatingTrainLateMins + NotStartedTrainLateMins) / (NotStartedTrainLateArr + OperatingTrainLateArr));
21838  PerfFile << LocsNotReached << " locations that trains failed to reach (average lateness " << AvLateMinsLocsNotReached.c_str() << " min)" << '\n';
21839  }
21840  if(SPADRisks != 1)
21841  {
21842  PerfFile << SPADRisks << " SPAD risks" << '\n';
21843  }
21844  else
21845  {
21846  PerfFile << SPADRisks << " SPAD risk" << '\n';
21847  }
21848  if(SPADEvents != 1)
21849  {
21850  PerfFile << SPADEvents << " SPADs" << '\n';
21851  }
21852  else
21853  {
21854  PerfFile << SPADEvents << " SPAD" << '\n';
21855  }
21856  if(Derailments != 1)
21857  {
21858  PerfFile << Derailments << " derailments" << '\n';
21859  }
21860  else
21861  {
21862  PerfFile << Derailments << " derailment" << '\n';
21863  }
21864  if(CrashedTrains != 1)
21865  {
21866  PerfFile << CrashedTrains << " crashed trains" << '\n';
21867  }
21868  else
21869  {
21870  PerfFile << CrashedTrains << " crashed train" << '\n';
21871  }
21872  PerfFile << '\n' << "***************************************" << '\n';
21873 
21874  bool DerailSPADFlag = false, CrashFlag = false;
21875 
21876  int OverallScorePercent = 100;
21877  int TotArrDepExit = 0;
21878  double TotLateMinsFactor = 1;
21879  double MissedStopAndSPADRiskFactor = 1;
21880  double NetNegFactor = 1;
21881 
21883  EarlyExits + LateExits + OnTimeExits; //exits added at v2.9.1, passes not counted
21884  // TotArrDep: total number of arrivals & departures including those for trains that haven't reached their destinations yet and are late
21885  // changed at v1.1.4 - calc was inside "if(OverallScorePercent == 100).." block so could remain 0 for SPADs & crashes, & then received the
21886  // 'no timetabled departures... message, which was inappropriate
21887 
21888  if((SPADEvents > 0) || (Derailments > 0))
21889  {
21890  OverallScorePercent = 5; // overrides other calculations
21891  DerailSPADFlag = true;
21892  }
21893  if(CrashedTrains > 0)
21894  {
21895  OverallScorePercent = 0; // overrides other calculations
21896  CrashFlag = true;
21897  }
21898  if(OverallScorePercent == 100)
21899  {
21900  int LatenessPenalty = TotLateArrMins + TotLateDepMins; //added at v2.13.0 for random delays
21901  if(Utilities->CumulativeDelayedRandMinsAllTrains > LatenessPenalty)
21902  {
21903  LatenessPenalty = 0;
21904  }
21905  else
21906  {
21907  LatenessPenalty -= Utilities->CumulativeDelayedRandMinsAllTrains;
21908  }
21909  if(TotArrDepExit > 0)
21910  {
21911  TotLateMinsFactor = exp((-0.1732) * (LatenessPenalty + OperatingTrainLateMins + NotStartedTrainLateMins + TotLateExitMins +
21912  ((OtherMissedEvents + SkippedTTEvents + UnexpectedExits + ExcessLCDownMins) * 15)) / TotArrDepExit); //exits added at v2.9.1
21913  // TotLateMinsFactor: negative exponential factor based on overall average arr & dep minutes late (with OtherMissedEvents & UnexpectedExits
21914  // counting as 15 mins late each), where 4 mins late average = half, 8 mins late = a quarter etc
21915  MissedStopAndSPADRiskFactor = exp((-17.33) * (MissedStops + SPADRisks + IncorrectExits) / TotArrDepExit);
21916  // MissedEventAndSPADRiskFactor: negative exponential factor based on number of missed stops, SPAD risks & IncorrectExits as a proportion
21917  // of arrivals & departures, where 4% = half, 8% = a quarter etc
21918  NetNegFactor = TotLateMinsFactor * MissedStopAndSPADRiskFactor;
21919  // NetNegfactor: product of the above two
21920  OverallScorePercent = 100 * NetNegFactor;
21921  }
21922  }
21923  if((TotArrDepExit > 0) || DerailSPADFlag || CrashFlag)
21924  // flag condits added at v1.1.4 - see above for what the error was
21925  {
21926  AnsiString OneFailureString = ", though the failure would account for some poor performance";
21927  AnsiString TwoOrMoreFailureString = ", though the failures would account for some poor performance";
21928  AnsiString AddedString = "";
21929  if(NumFailures == 1)
21930  {
21931  AddedString = OneFailureString;
21932  }
21933  if(NumFailures > 1)
21934  {
21935  AddedString = TwoOrMoreFailureString;
21936  }
21937  PerfFile << "\nOverall score: " << OverallScorePercent << "%\n";
21938  AnsiString Rating = "";
21939  if(OverallScorePercent == 100)
21940  {
21941  Rating = "Perfect!";
21942  }
21943  else if(OverallScorePercent >= 95)
21944  {
21945  Rating = "Excellent";
21946  }
21947  else if(OverallScorePercent >= 90)
21948  {
21949  Rating = "Very good";
21950  }
21951  else if(OverallScorePercent >= 80)
21952  {
21953  Rating = "Good";
21954  }
21955  else if(OverallScorePercent >= 70)
21956  {
21957  Rating = "Fair";
21958  }
21959  else if(OverallScorePercent >= 60)
21960  {
21961  Rating = "Unacceptable" + AddedString;
21962  }
21963  else if(OverallScorePercent >= 50)
21964  {
21965  Rating = "Poor" + AddedString;
21966  }
21967  else if(OverallScorePercent >= 40)
21968  {
21969  Rating = "Bad" + AddedString;
21970  }
21971  else if(OverallScorePercent >= 30)
21972  {
21973  Rating = "Very bad" + AddedString;
21974  }
21975  else if(OverallScorePercent >= 20)
21976  {
21977  Rating = "Terrible" + AddedString;
21978  }
21979  else if(OverallScorePercent >= 10)
21980  {
21981  Rating = "Appalling" + AddedString;
21982  }
21983  else if(OverallScorePercent >= 5)
21984  {
21985  if(DerailSPADFlag)
21986  {
21987  Rating = "Disastrous - potential loss of life";
21988  }
21989  // SPADs/Derailments
21990  else
21991  {
21992  Rating = "Dire" + AddedString;
21993  }
21994  }
21995  else if(OverallScorePercent < 5)
21996  {
21997  if(CrashFlag)
21998  {
21999  Rating = "Catastrophic - loss of life"; // Crashes
22000  }
22001  else
22002  {
22003  Rating = "Abysmal";
22004  }
22005  }
22006  PerfFile << "Overall rating: " << Rating.c_str() << '\n';
22007  }
22008  else
22009  {
22010  PerfFile << "\nThere were no timetabled departures, arrivals or exits so there is insufficient information to provide a performance score or rating" << '\n';
22011  }
22012  PerfFile << '\n' << "***************************************";
22013  PerfFile.flush();
22014  Utilities->CallLogPop(1736);
22015 }
22016 
22017 // ---------------------------------------------------------------------------
22018 
22020 {
22021  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SetWarningFlags");
22022  for(unsigned int x = 0; x < TrainVector.size(); x++)
22023  {
22024  TTrain &Train = TrainVectorAt(58, x);
22025  if(Train.Crashed)
22026  // can't use background colours for crashed & derailed because same colour
22027  {
22028  CrashWarning = true;
22029  }
22030  else if(Train.Derailed)
22031  // can't use background colours for crashed & derailed because same colour
22032  {
22033  DerailWarning = true;
22034  }
22035  else if(Train.BackgroundColour == clSPADBackground)
22036  // use colour as that changes as soon as passes signal
22037  {
22038  SPADWarning = true;
22039  }
22040  else if(Train.BackgroundColour == clTrainFailedBackground)
22041  {
22042  TrainFailedWarning = true;
22043  }
22044  else if(Train.BackgroundColour == clCallOnBackground)
22045  // use colour as also stopped at signal
22046  {
22047  CallOnWarning = true;
22048  }
22049  else if(Train.BackgroundColour == clSignalStopBackground)
22050  // use colour to distinguish from call-on
22051  {
22052  SignalStopWarning = true;
22053  }
22054  else if(Train.BackgroundColour == clBufferAttentionNeeded)
22055  // use colour to distinguish from ordinary buffer stop
22056  {
22057  BufferAttentionWarning = true;
22058  }
22059  }
22060  Utilities->CallLogPop(1796);
22061 }
22062 
22063 // ---------------------------------------------------------------------------
22064 
22066 {
22067  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CalcSignalStopLateness");
22068 
22069  // calculate lateness for running trains
22072  for(unsigned int x = 0; x < TrainVector.size(); x++)
22073  {
22074  TTrain &Train = TrainVectorAt(64, x);
22075  for(TActionVectorEntry * AVEntryPtr = &Train.TrainDataEntryPtr->ActionVector.front(); AVEntryPtr < &Train.TrainDataEntryPtr->ActionVector.back();
22076  AVEntryPtr++)
22077  {
22078  if(AVEntryPtr < Train.ActionVectorEntryPtr)
22079  {
22080  continue;
22081  }
22082  if((AVEntryPtr->ArrivalTime > TDateTime(-1)) && !Train.RevisedStoppedAtLoc() && (GetRepeatTime(42, AVEntryPtr->ArrivalTime, Train.RepeatNumber, Train.IncrementalMinutes) <
22083  TTClockTime))
22084  {
22085  OperatingTrainLateMins += 1440 * double(TTClockTime - GetRepeatTime(43, AVEntryPtr->ArrivalTime, Train.RepeatNumber, Train.IncrementalMinutes));
22087  }
22088 /* dropped departures after 2.7.0 because these don't count for 'failed to reach' numbers
22089  if((AVEntryPtr->DepartureTime > TDateTime(-1)) && (GetRepeatTime(44, AVEntryPtr->DepartureTime, Train.RepeatNumber, Train.IncrementalMinutes) <
22090  TTClockTime))
22091  {
22092  OperatingTrainLateMins += 1440 * double(TTClockTime - GetRepeatTime(45, AVEntryPtr->DepartureTime, Train.RepeatNumber, Train.IncrementalMinutes));
22093  OperatingTrainArrDep++;
22094  }
22095 */
22096  }
22097  }
22098 
22099  // calculate lateness for trains that haven't started yet (could be held awaiting entry)
22102 
22103  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
22104  {
22105  TTrainDataEntry & TDEntry = TrainDataVector.at(x);
22106  const TActionVectorEntry &AVEntryLast = TDEntry.ActionVector.at(TDEntry.ActionVector.size() - 1);
22107  int IncrementalMinutes = 0;
22108  if(AVEntryLast.FormatType == Repeat)
22109  {
22110  IncrementalMinutes = AVEntryLast.RearStartOrRepeatMins;
22111  }
22112  for(int y = 0; y < TDEntry.NumberOfTrains; y++)
22113  {
22114  TTrainOperatingData &TTOD = TDEntry.TrainOperatingDataVector.at(y);
22115  if(TTOD.RunningEntry != NotStarted)
22116  {
22117  continue;
22118  }
22119  // note that can't rely on the above for sessionfiles saved before v0.6b as wasn't set to Running for Sns/Fsp/rsp & shuttles
22120  // but if trains had exited then would be set to Exited, so need to check against trains still operating - use the test below
22121  bool TrainOperatingFlag = false;
22122  for(unsigned int a = 0; a < TrainController->TrainVector.size(); a++)
22123  {
22124  if((TrainController->TrainVector.at(a).TrainDataEntryPtr == &TDEntry) && (TrainController->TrainVector.at(a).RepeatNumber == y))
22125  {
22126  TrainOperatingFlag = true;
22127  break;
22128  }
22129  }
22130  if(TrainOperatingFlag)
22131  {
22132  continue;
22133  }
22134  if(GetRepeatTime(46, TDEntry.ActionVector.at(0).EventTime, y, IncrementalMinutes) > TTClockTime)
22135  {
22136  break; // if the first time is greater than TTClockTime then all the rest will also be greater (& default of -1 will be less so will be ignored)
22137  }
22138  for(unsigned int z = 0; z < TDEntry.ActionVector.size(); z++)
22139  {
22140  TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(z);
22141  if(GetRepeatTime(35, AVEntry.EventTime, y, IncrementalMinutes) > TTClockTime)
22142  {
22143  break; // all the rest will also be greater (& default of -1 will be less)
22144  }
22145  if(GetRepeatTime(36, AVEntry.ArrivalTime, y, IncrementalMinutes) > TTClockTime)
22146  {
22147  break; // all the rest will also be greater (& default of -1 will be less)
22148  }
22149  if(GetRepeatTime(37, AVEntry.DepartureTime, y, IncrementalMinutes) > TTClockTime)
22150  {
22151  break; // all the rest will also be greater (& default of -1 will be less)
22152  }
22153  if((AVEntry.ArrivalTime > TDateTime(-1)) && (GetRepeatTime(38, AVEntry.ArrivalTime, y, IncrementalMinutes) < TTClockTime))
22154  {
22155  NotStartedTrainLateMins += 1440 * double(TTClockTime - GetRepeatTime(39, AVEntry.ArrivalTime, y, IncrementalMinutes));
22157  }
22158 /* dropped departures after 2.7.0 as only interested in 'failed to reach' number - if train hasn't arrived then it hasn't departed so shouldn't count that as part of 'failed to reach'
22159  if((AVEntry.DepartureTime > TDateTime(-1)) && (GetRepeatTime(40, AVEntry.DepartureTime, y, IncrementalMinutes) < TTClockTime))
22160  {
22161  NotStartedTrainLateMins += 1440 * double(TTClockTime - GetRepeatTime(41, AVEntry.DepartureTime, y, IncrementalMinutes));
22162  NotStartedTrainArrDep++;
22163  }
22164 */
22165  }
22166  }
22167  }
22168  Utilities->CallLogPop(1894);
22169 }
22170 
22171 // ---------------------------------------------------------------------------
22172 
22174 // new v2.2.0 for OperatorActionPanel (OperatorActionPanel changed for ActionsDueForm at v2.13.0)
22175 // clears entries then adds values for running trains then for continuation entries
22176 // dont limit size here as need to check all trains (ActionsDueListBox is limited to 20 trains in Interface.cpp)
22177 {
22178  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RebuildOpTimeToActMultimap");
22179  OpTimeToActMultiMap.clear();
22180  TOpTimeToActMultiMapEntry OpTimeToActMultiMapEntry;
22181 
22182  if(!TrainVector.empty())
22183  // build OpTimeToActMultiMap entries for running trains
22184  {
22185  AnsiString HeadCode;
22186  // dropped in favour of TrainID for running trains int VecPos; //TrackVectorPosition of LeadElement or continuation where train is to enter
22187  int TrainID;
22188  THCandTrainPosParam HCandTrainPosParam;
22189  for(unsigned int x = 0; x < TrainVector.size(); x++)
22190  {
22191  HeadCode = TrainVectorAt(62, x).HeadCode;
22192  TrainID = TrainVectorAt(63, x).TrainID;
22193  HCandTrainPosParam.first = HeadCode;
22194  HCandTrainPosParam.second = TrainID;
22195  float TimeToAct = TrainVectorAt(65, x).OpTimeToAct;
22196  if((TimeToAct >= 0) && (TimeToAct < 59.9))
22197  // -1 indicates don't display
22198  {
22199  OpTimeToActMultiMapEntry.first = TimeToAct;
22200  OpTimeToActMultiMapEntry.second = HCandTrainPosParam;
22201  OpTimeToActMultiMap.insert(OpTimeToActMultiMapEntry);
22202  }
22203  }
22204  }
22205 /*
22206  * class TContinuationTrainExpectationEntry
22207  {
22208  public:
22209  AnsiString Description; ///< service description
22210  AnsiString HeadCode; ///< service headcode
22211  int RepeatNumber; ///< service RepeatNumber
22212  int IncrementalMinutes; ///< Repeat separation in minutes
22213  int IncrementalDigits; ///< Repeat headcode separation
22214  int VectorPosition; ///< TrackVectorPosition for the continuation element
22215  TTrainDataEntry *TrainDataEntryPtr; ///< points to the service entry in the timetable's TrainDataVector
22216  };
22217 
22218  Multimap class for TContinuationTrainExpectationEntry objects, where the access key is the expectation time
22219  typedef std::multimap<TDateTime, TContinuationTrainExpectationEntry> TContinuationTrainExpectationMultiMap;
22220  typedef TContinuationTrainExpectationMultiMap::iterator TContinuationTrainExpectationMultiMapIterator; ///< iterator for the multimap
22221  typedef std::pair<TDateTime, TContinuationTrainExpectationEntry> TContinuationTrainExpectationMultiMapPair; ///< a single multimap entry
22222 */
22223 
22225  // build OpTimeToActMultiMap entries for expected trains
22226  {
22227  // note that using the ContinuationTrainExpectationMultiMap automatically ensures that entries are in ascending time order
22228  // first have to calculate times to red signal for each train due to enter (ignore later trains as will likely change before they are due)
22229  float TimeToAct = 0; // minutes
22230  int DistanceToRedSignal = 0; // metres
22231  TContinuationEntryVecPosVector ContinuationEntryVecPosVector;
22232  // used to ensure only one train displayed for a given continuation
22233  ContinuationEntryVecPosVector.clear();
22234  bool LaterTrain = false;
22237  {
22238  LaterTrain = false;
22239  if(CTEIt->second.TrainDataEntryPtr->TrainOperatingDataVector.at(CTEIt->second.RepeatNumber).RunningEntry != NotStarted)
22240  {
22241  CTEIt++;
22242  continue; // not interested in running or exited trains
22243  }
22244  if(Track->TrackElementAt(934, CTEIt->second.VectorPosition).TrainIDOnElement > 0)
22245  {
22246  CTEIt++;
22247  continue;
22248  // don't include trains not entered yet when a train is already on the continuation
22249  }
22250  if(!ContinuationEntryVecPosVector.empty())
22251  {
22252  for(unsigned int x = 0; x < ContinuationEntryVecPosVector.size(); x++)
22253  {
22254  if(CTEIt->second.VectorPosition == ContinuationEntryVecPosVector.at(x))
22255  {
22256  LaterTrain = true;
22257  ;
22258  // skip past remaining trains waiting to enter at same point
22259  break;
22260  }
22261  }
22262  }
22263  if(LaterTrain)
22264  {
22265  CTEIt++;
22266  continue;
22267  }
22268  ContinuationEntryVecPosVector.push_back(CTEIt->second.VectorPosition);
22269  AnsiString HeadCode = CTEIt->second.HeadCode;
22270  float CurrentStopTime; // set to 0 at start of function
22271  float LaterStopTime; // set to 0 at start of function
22272  float RecoverableTime; // set to 0 at start of function
22273  int AvTrackSpeed; // set to 0 at start of function
22274  int TrainID = -1; // not yet allocated for train still to enter
22275  int DistanceToExit; //not used for continuation entries
22276  THVShortPair ExitPair;
22277  bool SigControlAndCanPassRedSignal = false; // doesn't apply for a continuation
22278 
22279 //at v2.11.0 found that with *AVPtr set to ...ActionVector.at(0) below instead of ...at(1) to stop signaller control trains throwing an error (because there is no ...at(1) -
22280 //discovered with Birmingham) the LaterStopTime isn't calculated and if a train does something other than depart after an arrival it is still listed in the actions due panel -
22281 //because it just calcs the distance to the red signal and converts that to a time. So here (after v2.11.0) this new test is introduced to determine whether a train is a
22282 //signaller control train (when the ActionVector size is 1) or not (ActionVector size > 1), and the ...at(value) is set accordingly - 0 for signaller control or 1 if not.
22283 
22284  int AtValue = 1;
22285  if(CTEIt->second.TrainDataEntryPtr->ActionVector.size() == 1)
22286  {
22287  AtValue = 0;
22288  }
22289  DistanceToRedSignal = CalcDistanceToRedSignalandStopTime(1, CTEIt->second.VectorPosition, 0,
22290  // EntryPos always 0 for entering at a continuation
22291  SigControlAndCanPassRedSignal, &CTEIt->second.TrainDataEntryPtr->ActionVector.at(AtValue), //see above note
22292  HeadCode, TrainID, CurrentStopTime, LaterStopTime, RecoverableTime, AvTrackSpeed, DistanceToExit, ExitPair);
22293  // for above VectorPosition is the first element to have its length included in the sum, so for a continuation it's the continuation itself
22294  // for a train it's the one in front of LeadElement
22295  if(AvTrackSpeed < 30)
22296  {
22297  AvTrackSpeed = 30;
22298  }
22299  if(DistanceToRedSignal == -1)
22300  {
22301  TimeToAct = 60.0;
22302  }
22303  else
22304  {
22305  int Speed = AvTrackSpeed;
22306  int MaxSpeed = int(CTEIt->second.TrainDataEntryPtr->MaxRunningSpeed);
22307  if(AvTrackSpeed > MaxSpeed)
22308  {
22309  Speed = MaxSpeed;
22310  }
22311  if(CTEIt->second.TrainDataEntryPtr->ActionVector.at(0).SignallerControl) //changed to ...at(0) from at(1) at v2.11.0 as SignallerControl only valid for ..at(0)
22312  // defined in timetable as under signaller control
22313  {
22314  Speed = CTEIt->second.TrainDataEntryPtr->SignallerSpeed;
22315  LaterStopTime = 0;
22316  }
22317  TimeToAct = LaterStopTime + DistanceToRedSignal * 3.6 / 60 / Speed;
22318  // accel & decel taken into account in
22319  // CalcDistanceToRedSignalandStopTime
22320  // 3.6 convertsKm/h to m/s & 60 converts seconds to minutes
22321  // don't need CurrentStopTime or RecoverableTime for continuation entries
22322  float MinsBefEnter = double(CTEIt->first - TTClockTime) * 86400.0 / 60.0;
22323  TimeToAct += MinsBefEnter;
22324  }
22325  THCandTrainPosParam HCandTrainPosParam;
22326  HCandTrainPosParam.first = HeadCode;
22327  HCandTrainPosParam.second = -1 - CTEIt->second.VectorPosition;
22328  // -1-CTE... because 2nd value covers TrainID if +ve &
22329  // continuation track vector position if -ve, -1 allows for vecpos being 0
22330  if(TimeToAct < 59.9) // if 60 don't enter a value in multimap
22331  {
22332  OpTimeToActMultiMapEntry.first = TimeToAct;
22333  OpTimeToActMultiMapEntry.second = HCandTrainPosParam;
22334  OpTimeToActMultiMap.insert(OpTimeToActMultiMapEntry);
22335  }
22336  CTEIt++;
22337  }
22338  }
22339  Utilities->CallLogPop(2081);
22340 }
22341 
22342 // ---------------------------------------------------------------------------
22343 
22345 // new for multiplayer
22346 // clears entries then adds values for running trains
22347 {
22348  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RebuildTimeToExitMultiMap");
22349  TimeToExitMultiMap.clear();
22350  TTimeToExitMultiMapEntry TimeToExitMultiMapEntry;
22351 
22352  if(!TrainVector.empty())
22353  // build map entries for running trains
22354  {
22355  TExitInfo ExitInfo; //corresponds to TServiceInfo in Interface
22356  THVShortPair ExitPair;
22357  float TimeToExit;
22358  for(unsigned int x = 0; x < TrainVector.size(); x++)
22359  {
22361  ExitInfo.RepeatNumber = short(TrainVectorAt(81, x).RepeatNumber);
22362  ExitInfo.TimeToExitSecs = short(TrainVectorAt(77, x).TimeToExit * 60);
22363  ExitPair = TrainVectorAt(76, x).ExitPair;
22364  if((ExitInfo.TimeToExitSecs >= 3570) || (ExitInfo.TimeToExitSecs < 1)) //59.5 mins or -60 secs
22365  {
22366  ExitInfo.TimeToExitSecs = -1;
22367  }
22368  TimeToExitMultiMapEntry.first = ExitPair;
22369  TimeToExitMultiMapEntry.second = ExitInfo;
22370  TimeToExitMultiMap.insert(TimeToExitMultiMapEntry);
22371  }
22372  }
22373  Utilities->CallLogPop(2323);
22374 }
22375 
22376 // ---------------------------------------------------------------------------
22377 
22378 int TTrainController::CalcDistanceToRedSignalandStopTime(int Caller, int TrackVectorPosition, int TrackVectorPositionEntryPos,
22379  bool SigControlAndCanPassRedSignal, TActionVectorEntry *AVPtr, AnsiString HeadCode, int TrainID, float &CurrentStopTime, float &LaterStopTime,
22380  float &RecoverableTime, int &AvTrackSpeed, int &DistanceToExit, THVShortPair &ExitPair)
22381 // new v2.2.0
22382 // vectorPosition is the value for the first element to be measured - for a continuation it's the continuation itself, for a train
22383 // it's the one after LeadElement, returns -1 for infinity - i.e. not approaching red signal (e.g. maybe cdt before reach it).
22384 // CurrentStopTime is the time to depart from the current station (if stopped at a station) and LaterStopTime is the total station
22385 // stop times for stations after the current one. DistanceToRedSignal is what the name implies, if -1 is returned the other values
22386 // aren't used - this means there is no display for the train in question
22387 {
22388  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",DistanceToRedSignal, " + AnsiString(TrackVectorPosition) + ", " +
22389  AnsiString(TrackVectorPositionEntryPos) + ", " + AVPtr->Command);
22390  int DistanceToRedSignal = 0;
22391  DistanceToExit = -1;
22392  ExitPair.first = -1;
22393  ExitPair.second = -1;
22394  int CumTrackSpeed = 0;
22395  // average track speed, in case need to use in time calc
22396  int TrackSpeedCount = 0;
22397  float KmPerLocationStop;
22398  float MaxAllowableSpeed;
22399 
22400  //below added at v2.6.1
22401  if(TrainID > -1) //will be -1 for trains not entered yet
22402  {
22403  TTrain &Train = TrainVectorAtIdent(51, TrainID);
22404  Train.DistanceToStationStop = 0; //if find a red signal first then this distance isn't needed
22405  Train.StationStopCalculated = false;
22406  }
22407  AvTrackSpeed = 0;
22408  int CurrentElement = TrackVectorPosition;
22409  int CurrentEntryPos = TrackVectorPositionEntryPos;
22410  int NextElement;
22411  int NextEntryPos;
22412  int NextExitPos;
22413 
22414  CurrentStopTime = 0;
22415  LaterStopTime = 0;
22416  RecoverableTime = 0;
22417  if(CurrentElement == -1) // train on end element, no action needed
22418  {
22419  Utilities->CallLogPop(2094);
22420  return(-1);
22421  }
22422  int CurrentExitPos;
22423 
22424  // get ExitPos for first element to be measured
22425  if(Track->TrackElementAt(935, CurrentElement).TrackType == Points)
22426  {
22427  if((CurrentEntryPos == 0) || (CurrentEntryPos == 2)) // leading point
22428  {
22429  if(Track->TrackElementAt(936, CurrentElement).Attribute == 0)
22430  {
22431  CurrentExitPos = 1;
22432  }
22433  else
22434  {
22435  CurrentExitPos = 3;
22436  }
22437  }
22438  else
22439  {
22440  CurrentExitPos = 0; // trailing point
22441  }
22442  }
22443  else
22444  {
22445  CurrentExitPos = Track->GetNonPointsOppositeLinkPos(CurrentEntryPos);
22446  }
22447  // get CumTrackSpeed for first measured element
22448 
22449  TConfiguration CurrentExitConfig = Track->TrackElementAt(937, CurrentElement).Config[CurrentExitPos];
22450  int CurrentAttribute = Track->TrackElementAt(938, CurrentElement).Attribute;
22451  bool CurrentElementFailed = Track->TrackElementAt(1549, CurrentElement).Failed; //added at v2.13.2
22452 
22453  // check if currently stopped at a location, and if so add the remaining dwell time
22454  // can't use CurrentElement as that is in front of LeadElement and might not be at the location
22455  if(TrainID > -1)
22456  // -1 for a continuation and can't be at a location as not yet entered
22457  {
22458  TTrain &Train = TrainVectorAtIdent(39, TrainID); //Train wasn't a reference before v2.6.1 mods so FirstLaterStopRecoverableTime wouldn't be reset for the referenced train
22460  // this used to deduct from RecoverableTime when arrive at a location
22461  if(Train.RevisedStoppedAtLoc())
22462  {
22463  if(Train.StoppedForTrainInFront || Train.TrainInFront)
22464  {
22465  Utilities->CallLogPop(2082);
22466  return(-1); // no action needed
22467  }
22469  { //added '|| (Train.ActionVectorEntryPtr->FormatType == TimeCmdDescription)' at v2.16.1 so description ignored in calculating action due time
22470  //added TimeCmdMaxSpeed at v2.21.0
22471  Utilities->CallLogPop(2083);
22472  return(-1); // not due a departure, description change or a max speed change so no action needed
22473  }
22474  else if(((Train.ActionVectorEntryPtr + 1)->FormatType == TimeLoc) && ((Train.ActionVectorEntryPtr->FormatType == TimeCmdDescription) || Train.ActionVectorEntryPtr->FormatType == TimeCmdMaxSpeed)) // due a departure immediately after change of description or change of max speed
22475  { //added at v2.16.1 to cover description change due next then a departure //added TimeCmsMaxSpeed at v2.21.0
22476  double TimeToDepart = double((Train.GetTrainTime(68, (Train.ActionVectorEntryPtr + 1)->DepartureTime)) - TrainController->TTClockTime) * 86400 / 60; // mins to depart excluding possible 30sec allowance from LastActionTime
22477  //need repeat time for the above
22478  if((Train.ActionVectorEntryPtr + 1)->DepartureTime == Train.ActionVectorEntryPtr->EventTime) //don't need repeat time here
22479  {
22480  TimeToDepart+= 0.5; //add in the 30 secs if depature time same as description or max speed change time
22481  }
22482  if(TimeToDepart < 0.5)
22483  {
22484  TimeToDepart = 0.5;
22485  }
22486  // can't convert a TDateTime to a float directly
22487  CurrentStopTime = float(TimeToDepart);
22488  AVPtr++;
22489  AVPtr++;
22490  }
22491  else if((Train.ActionVectorEntryPtr->FormatType == TimeLoc) || (Train.ActionVectorEntryPtr->FormatType == TimeTimeLoc)) // due a departure as next action
22492  {
22493  double TimeToDepart = double(Train.ReleaseTime - TrainController->TTClockTime) * 86400 / 60; // mins to depart
22494  // can't convert a TDateTime to a float directly
22495  CurrentStopTime = float(TimeToDepart);
22496  AVPtr++;
22497  }
22498  else //added at v2.16.1 to catch all other combinations
22499  { //none of the above so no action needed
22500  Utilities->CallLogPop(2628);
22501  return(-1);
22502  }
22503  }
22504  }
22505  // check if CurrentElement is a red signal, but ok if autosig route after provided signal not failed
22506  if((CurrentExitConfig == Signal) && (CurrentAttribute == 0))
22507  // ok if autosig route after red signal unless signal has failed
22508  {
22509  int NextElement = Track->TrackElementAt(939, CurrentElement).Conn[CurrentExitPos];
22510  int NextEntryPos = Track->TrackElementAt(940, CurrentElement).ConnLinkPos[CurrentExitPos];
22511  int RouteNumber; // holder for referenced value, not used
22512  if((AllRoutes->GetRouteTypeAndNumber(33, NextElement, NextEntryPos, RouteNumber) == TAllRoutes::AutoSigsRoute) && !CurrentElementFailed)
22513  { //CurrentElementFailed added at v2.13.2
22514  Utilities->CallLogPop(2078);
22515  return(-1);
22516  }
22517  else if(SigControlAndCanPassRedSignal)
22518  // ignore signal and increment CurrentElement to NextElement
22519  {
22520  if(Track->TrackElementAt(941, NextElement).TrackType == Points)
22521  {
22522  if((NextEntryPos == 0) || (NextEntryPos == 2))
22523  // leading entry point
22524  {
22525  if(Track->TrackElementAt(942, NextElement).Attribute == 0)
22526  {
22527  NextExitPos = 1;
22528  }
22529  else
22530  {
22531  NextExitPos = 3;
22532  }
22533  }
22534  else
22535  {
22536  NextExitPos = 0; // trailing entry point
22537  }
22538  }
22539  else
22540  {
22541  NextExitPos = Track->GetNonPointsOppositeLinkPos(NextEntryPos);
22542  }
22543  CurrentElement = NextElement;
22544  CurrentEntryPos = NextEntryPos;
22545  CurrentExitPos = NextExitPos;
22546  CurrentExitConfig = Track->TrackElementAt(943, CurrentElement).Config[CurrentExitPos];
22547  CurrentAttribute = Track->TrackElementAt(944, CurrentElement).Attribute;
22548  }
22549  else if((TrainID > -1) && (TrainVectorAtIdent(40, TrainID).TrainMode == Timetable)) // ignore signallercontrol or will
22550  // give 'NOW' indication after allowed to pass stop signal when LeadMidLag (AllowedToPassRedSignal reset by this point)
22551  {
22552  Utilities->CallLogPop(2084);
22553  return(0);
22554  // stopped with red signal in front, don't need AvSpeedLimit in this case, & if at location awaiting departure dwell time already calculated
22555  }
22556  }
22557  int LaterStopNumber = 0;
22558  int x = 0;
22559  // added in v2.4.0 to prevent endless circling round track loops - spotted by Xeon 09/03/20 & reported by emsil
22560 
22561  while(!((CurrentExitConfig == Signal) && (CurrentAttribute == 0)))
22562  // not red signal next (in fwd direction) so enter loop to calc CumLength
22563  {
22564  x++; // added in v2.4.0 as above
22565  if(x > 5000)
22566  {
22567  Utilities->CallLogPop(2120);
22568  return(-1);
22569  }
22570  if(CurrentEntryPos > 1)
22571  {
22572  DistanceToRedSignal += Track->TrackElementAt(916, CurrentElement).Length23;
22573  CumTrackSpeed += Track->TrackElementAt(945, CurrentElement).SpeedLimit23;
22574  }
22575  else
22576  {
22577  DistanceToRedSignal += Track->TrackElementAt(917, CurrentElement).Length01;
22578  CumTrackSpeed += Track->TrackElementAt(946, CurrentElement).SpeedLimit01;
22579  }
22580  TrackSpeedCount++;
22581 
22582  //added for multiplayer - exiting at a continuation and continuation length already added
22583  if((Track->TrackElementAt(1407, CurrentElement).TrackType == Continuation) && (Track->TrackElementAt(1408, CurrentElement).Config[CurrentExitPos] == End))
22584  {
22585  DistanceToExit = DistanceToRedSignal; //don't need to exit function here as will exit when find that the next Conn value is -1
22586  ExitPair.first = Track->TrackElementAt(1409, CurrentElement).HLoc;
22587  ExitPair.second = Track->TrackElementAt(1410, CurrentElement).VLoc;
22588  //here repeat calcs for MaxAllowableSpeed & AvTrackSpeed as done at end for stop signal
22589  //need here as next element will be -1 so will exit before calcs at end
22590  if(TrackSpeedCount > 0)
22591  {
22592  MaxAllowableSpeed = CumTrackSpeed / TrackSpeedCount;
22593  }
22594  else // shouldn't reach here but include to prevent divide by zero error
22595  {
22596  if(CurrentEntryPos > 1)
22597  {
22598  MaxAllowableSpeed = Track->TrackElementAt(951, CurrentElement).SpeedLimit23;
22599  }
22600  else
22601  {
22602  MaxAllowableSpeed = Track->TrackElementAt(952, CurrentElement).SpeedLimit01;
22603  }
22604  // Train MaxRunningSpeed taken into account in RebuildOpTimeToActMultimap
22605  }
22606  //calc AvTrackSpeed
22607  if(LaterStopNumber > 0)
22608  {
22609  KmPerLocationStop = float(DistanceToRedSignal) / LaterStopNumber / 1000; // m to km
22610  AvTrackSpeed = (8.75 * KmPerLocationStop) + 44;
22611  // Av speed calculation based on formula: Speed = 8.75*(kms/location stop) + 44 km/sec (from experiments), subject to a maximum of
22612  // average line speed/2 (for half distance accelerating and half decelerating.
22613  }
22614  else
22615  {
22616  AvTrackSpeed = (sqrt(float(DistanceToRedSignal) / 1000) * 44) + 60;
22617  // using linear trendline for accel & decel distance at various speeds
22618  // at half braking, speed never < 60 using this
22619  }
22620  if(AvTrackSpeed > MaxAllowableSpeed)
22621  {
22622  AvTrackSpeed = MaxAllowableSpeed;
22623  }
22624  }
22625 
22626  // added at v2.6.1 to find DistanceToStationStop for trains running early
22627  if(TrainID > -1) //can ignore continuation entries as these don't run early
22628  {
22629  TTrain &Train = TrainVectorAtIdent(52, TrainID);
22630  if(!Train.StationStopCalculated)
22631  {
22632  if(Train.TrainMode == Timetable)
22633  {
22634  bool StopRequired = false;
22635  if(!Train.TimetableFinished && (Train.NameInTimetableBeforeCDT(16, Track->TrackElementAt(1005, CurrentElement).ActiveTrackElementName,
22636  StopRequired) > -1) && ((Track->TrackElementAt(1006, CurrentElement).StationEntryStopLinkPos1 == CurrentEntryPos) ||
22637  (Track->TrackElementAt(1010, CurrentElement).StationEntryStopLinkPos2 == CurrentEntryPos) ||
22638  (Track->TrackElementAt(1654, CurrentElement).StationEntryStopLinkPos3 == CurrentEntryPos) ||
22639  (Track->TrackElementAt(1655, CurrentElement).StationEntryStopLinkPos4 == CurrentEntryPos)))
22640  {
22641  // no need to add in the length of element to CumulativeLength
22642  if(StopRequired)
22643  {
22644  Train.DistanceToStationStop = DistanceToRedSignal; // DistanceToRedSignal holds the intermediate distance to this point
22645  Train.StationStopCalculated = true; //don't want to update it with later stops
22646  }
22647  }
22648  }
22649  }
22650  }
22651  // check for train in front, but if on a bridge on other track then ok
22652  TTrackElement TE = Track->TrackElementAt(947, CurrentElement);
22653  int TrainOnElement;
22654  if(TE.TrackType != Bridge)
22655  {
22656  TrainOnElement = TE.TrainIDOnElement;
22657  }
22658  else
22659  {
22660  if(CurrentEntryPos > 1)
22661  {
22663  }
22664  else
22665  {
22667  }
22668  }
22669  if((TrainOnElement > -1) && (TrainOnElement != TrainID))
22670  // train in front before red signal
22671  {
22672  Utilities->CallLogPop(2085);
22673  return(-1);
22674  }
22675  // add to stoptime if required
22676  if(Track->TrackElementAt(948, CurrentElement).ActiveTrackElementName != "")
22677  {
22678  double StopTimeDouble;
22679  while(AVPtr->FormatType == PassTime)
22680  {
22681  AVPtr++; // skip past any passes
22682  }
22683  if((Track->TrackElementAt(949, CurrentElement).ActiveTrackElementName == AVPtr->LocationName) && ((AVPtr->FormatType == TimeLoc) ||
22684  (AVPtr->FormatType == TimeTimeLoc)))
22685  // stop due here so calc dwell time & advance Ptr
22686  {
22687  if(AVPtr->FormatType == TimeTimeLoc)
22688  {
22689  LaterStopNumber++;
22690  StopTimeDouble = double(AVPtr->DepartureTime - AVPtr->ArrivalTime) * 86400.0 / 60.0;
22691  if(StopTimeDouble < 0.5)
22692  {
22693  StopTimeDouble = 0.5;
22694  }
22695  // at least 30 secs delay at station
22696  // can't convert a TDateTime to a float directly
22697  LaterStopTime += float(StopTimeDouble);
22698  RecoverableTime += StopTimeDouble - 0.5;
22699  if((LaterStopNumber == 1) && (TrainID > -1))
22700  {
22701  TrainVectorAtIdent(41, TrainID).FirstLaterStopRecoverableTime = RecoverableTime;
22702  }
22703  AVPtr++;
22704  }
22705  else if((AVPtr->FormatType == TimeLoc) && (AVPtr->ArrivalTime != TDateTime(-1))) // must be an arrival
22706  {
22707  if((AVPtr + 1)->FormatType == TimeLoc)
22708  // must be a departure
22709  {
22710  LaterStopNumber++;
22711  StopTimeDouble = double((AVPtr + 1)->DepartureTime - AVPtr->ArrivalTime) * 86400.0 / 60.0; //diff will be same for all repeats
22712  // can't convert a TDateTime to a float directly //so repeat times not required
22713  if(TrainID > -1) //exclude trains still to enter
22714  {
22715  TTrain &Train = TrainVectorAtIdent(67, TrainID);
22716  if(TTClockTime > Train.GetTrainTime(69, AVPtr->ArrivalTime)) //running late, added at v2.16.1
22717  {
22718  StopTimeDouble = double(Train.GetTrainTime(70, (AVPtr + 1)->DepartureTime) - TTClockTime) * 86400.0 / 60.0; //may be -ve but if so it's set to 0.5 later
22719  // can't convert a TDateTime to a float directly
22720  }
22721  }
22722  if(StopTimeDouble < 0.5)
22723  {
22724  StopTimeDouble = 0.5;
22725  }
22726  // at least 30 secs delay at station
22727  LaterStopTime += float(StopTimeDouble);
22728  RecoverableTime += StopTimeDouble - 0.5;
22729  if((LaterStopNumber == 1) && (TrainID > -1))
22730  {
22731  TrainVectorAtIdent(42, TrainID).FirstLaterStopRecoverableTime = RecoverableTime;
22732  }
22733  AVPtr++;
22734  AVPtr++;
22735  }
22736  else if((((AVPtr + 1)->FormatType == TimeCmdDescription) || ((AVPtr + 1)->FormatType == TimeCmdMaxSpeed)) && ((AVPtr + 2)->FormatType == TimeLoc)) //change of description or max speed then departure
22737  { //added at v2.16.1 so description changes ignored in calculating time to act //added TimeCmdMaxSpeed at v2.21.0
22738  LaterStopNumber++;
22739  StopTimeDouble = double((AVPtr + 2)->DepartureTime - AVPtr->ArrivalTime) * 86400.0 / 60.0; //diff will be same for all repeats
22740  // can't convert a TDateTime to a float directly //so repeat times not required
22741  if(TrainID > -1) //exclude trains still to enter
22742  {
22743  TTrain &Train = TrainVectorAtIdent(68, TrainID);
22744  if(TTClockTime > Train.GetTrainTime(71, AVPtr->ArrivalTime)) //running late, added at v2.16.1
22745  {
22746  StopTimeDouble = double(Train.GetTrainTime(72, (AVPtr + 2)->DepartureTime) - TTClockTime) * 86400.0 / 60.0; //may be -ve but if so it's set to 0.5 later
22747  // can't convert a TDateTime to a float directly
22748  }
22749  }
22750  if(StopTimeDouble < 0.5)
22751  {
22752  StopTimeDouble = 0.5;
22753  }
22754  // at least 30 secs delay at station
22755  LaterStopTime += float(StopTimeDouble);
22756  RecoverableTime += StopTimeDouble - 0.5;
22757  if((LaterStopNumber == 1) && (TrainID > -1))
22758  {
22759  TrainVectorAtIdent(69, TrainID).FirstLaterStopRecoverableTime = RecoverableTime;
22760  }
22761  AVPtr++;
22762  AVPtr++;
22763  AVPtr++;
22764  }
22765  else // does something else at the location so no calculation needed
22766  {
22767  Utilities->CallLogPop(2086);
22768  return(-1);
22769  }
22770  }
22771  }
22772  }
22773  NextElement = Track->TrackElementAt(950, CurrentElement).Conn[CurrentExitPos];
22774  if(NextElement == -1) // reached end element, no action needed
22775  {
22776  Utilities->CallLogPop(2077);
22777  return(-1);
22778  }
22779  NextEntryPos = Track->TrackElementAt(919, CurrentElement).ConnLinkPos[CurrentExitPos];
22780  // get NextExitPos
22781  if(Track->TrackElementAt(920, NextElement).TrackType == Points)
22782  {
22783  if((NextEntryPos == 0) || (NextEntryPos == 2))
22784  // leading entry point
22785  {
22786  if(Track->TrackElementAt(921, NextElement).Attribute == 0)
22787  {
22788  NextExitPos = 1;
22789  }
22790  else
22791  {
22792  NextExitPos = 3;
22793  }
22794  }
22795  else
22796  {
22797  NextExitPos = 0; // trailing entry point
22798  }
22799  }
22800  else
22801  {
22802  NextExitPos = Track->GetNonPointsOppositeLinkPos(NextEntryPos);
22803  }
22804  CurrentElement = NextElement;
22805  CurrentEntryPos = NextEntryPos;
22806  CurrentExitPos = NextExitPos;
22807  CurrentExitConfig = Track->TrackElementAt(922, CurrentElement).Config[CurrentExitPos];
22808  CurrentAttribute = Track->TrackElementAt(923, CurrentElement).Attribute;
22809  CurrentElementFailed = Track->TrackElementAt(1550, CurrentElement).Failed; //added at v2.13.2
22810  }
22811  if((CurrentExitConfig == Signal) && (CurrentAttribute == 0))
22812  // ok if autosig route after red signal, no action needed
22813  {
22814  int NextElement = Track->TrackElementAt(924, CurrentElement).Conn[CurrentExitPos];
22815  int NextEntryPos = Track->TrackElementAt(925, CurrentElement).ConnLinkPos[CurrentExitPos];
22816  int RouteNumber; // holder for referenced value, not used
22817  if((AllRoutes->GetRouteTypeAndNumber(31, NextElement, NextEntryPos, RouteNumber) == TAllRoutes::AutoSigsRoute) && !CurrentElementFailed)
22818  { //CurrentElementFailed added at v2.13.2
22819  Utilities->CallLogPop(2095);
22820  return(-1);
22821  }
22822  }
22823 
22824  if(TrackSpeedCount > 0)
22825  {
22826  MaxAllowableSpeed = CumTrackSpeed / TrackSpeedCount;
22827  }
22828  else // shouldn't reach here but include to prevent divide by zero error
22829  {
22830  if(CurrentEntryPos > 1)
22831  {
22832  MaxAllowableSpeed = Track->TrackElementAt(1433, CurrentElement).SpeedLimit23;
22833  }
22834  else
22835  {
22836  MaxAllowableSpeed = Track->TrackElementAt(1434, CurrentElement).SpeedLimit01;
22837  }
22838  // Train MaxRunningSpeed taken into account in RebuildOpTimeToActMultimap
22839  }
22840 
22841  if(LaterStopNumber > 0)
22842  {
22843  KmPerLocationStop = float(DistanceToRedSignal) / LaterStopNumber / 1000; // m to km
22844  AvTrackSpeed = (8.75 * KmPerLocationStop) + 44;
22845  }
22846  else
22847  // Av speed calculation based on formula: Speed = 8.75*(kms/location stop) + 44 km/sec (from experiments), subject to a maximum of
22848  // average line speed/2 (for half distance accelerating and half decelerating.
22849  {
22850  AvTrackSpeed = (sqrt(float(DistanceToRedSignal) / 1000) * 44) + 60;
22851  // using linear trendline for accel & decel distance at various speeds
22852  // at half braking, speed never < 60 using this
22853  }
22854  if(AvTrackSpeed > MaxAllowableSpeed)
22855  {
22856  AvTrackSpeed = MaxAllowableSpeed;
22857  }
22858  Utilities->CallLogPop(2096);
22859  return(DistanceToRedSignal);
22860 }
22861 
22862 // ---------------------------------------------------------------------------
22863 // end of TTrainController entries
22864 // ---------------------------------------------------------------------------
TTrain::LinkOccupied
bool LinkOccupied(int Caller, int TrackVectorPosition, int LinkNumber)
Added at v1.2.0: true if any part of train on specific link, false otherwise, including no link prese...
Definition: TrainUnit.cpp:9571
TAllRoutes::TrackIsInARoute
bool TrackIsInARoute(int Caller, int TrackVectorPosition, int LinkPos)
Examines Route2MultiMap and if the element at TrackVectorPosition with LinkPos (can be entry or exit)...
Definition: TrackUnit.cpp:19922
TActionVectorEntry::EventTime
TDateTime EventTime
Definition: TrainUnit.h:137
TTrain::AllowedToPassRedSignal
bool AllowedToPassRedSignal
set when train has been called on, or when under signaller control and instructed to pass a red signa...
Definition: TrainUnit.h:389
JoinedByOther
@ JoinedByOther
Definition: TrainUnit.h:55
TTrainController::CheckShuttleRepeatTime
bool CheckShuttleRepeatTime(int Caller, TDateTime ForwardEventTime, TDateTime ReverseEventTime, int RepeatMinutes)
Check that shuttle link services have consistent times, true for success.
Definition: TrainUnit.cpp:16522
TTrain::ZeroPowerNoNewShuttleFromNonRepeatMessage
bool ZeroPowerNoNewShuttleFromNonRepeatMessage
Definition: TrainUnit.h:355
TTrain::ZeroPowerNoNewServiceMessage
bool ZeroPowerNoNewServiceMessage
Definition: TrainUnit.h:354
TTrain::VOffset
int VOffset[4]
each headcode character is an 8x8 pixel graphic and must be placed within a 16x16 pixel element,...
Definition: TrainUnit.h:509
TTrainController
Handles all train and timetable activities, only one object created.
Definition: TrainUnit.h:741
TTrain::TTrain
TTrain(int Caller, int RearStartElementIn, int RearStartExitPosIn, AnsiString InputCode, int StartSpeed, int Mass, double MaxRunningSpeed, double MaxBrakeRate, double PowerAtRail, TTrainMode TrainMode, TTrainDataEntry *TrainDataEntryPtr, int RepeatNumber, int IncrementalMinutes, int IncrementalDigits, int SignallerMaxSpeed)
Constructor, sets listed member values.
Definition: TrainUnit.cpp:73
TAllRoutes::LockedRouteVector
TLockedRouteVector LockedRouteVector
the vector that stores all the locked routes on the railway
Definition: TrackUnit.h:1752
TRailGraphics::smOrange
Graphics::TBitmap * smOrange
Definition: GraphicUnit.h:904
TActionVector
std::vector< TActionVectorEntry > TActionVector
contains all actions for a single train
Definition: TrainUnit.h:178
TUtilities::LoadFileString
AnsiString LoadFileString(std::ifstream &InFile)
loads a string value from the file
Definition: Utilities.cpp:190
TTrainController::CheckStartPositionValidity
bool CheckStartPositionValidity(int Caller, AnsiString RearElementStr, AnsiString FrontElementStr, bool GiveMessages)
A timetable validation function where train starting positions are checked for validity,...
Definition: TrainUnit.cpp:16184
TTrainDataEntry::FixedDescription
AnsiString FixedDescription
Definition: TrainUnit.h:213
TTrain::ChangeTrainDirection
void ChangeTrainDirection(int Caller, bool NoLogFlag)
Reverses the direction of motion of the train.
Definition: TrainUnit.cpp:6599
TActionVectorEntry::SplitDistribution
AnsiString SplitDistribution
Definition: TrainUnit.h:125
TTrain::MidEntryPos
int MidEntryPos
Definition: TrainUnit.h:377
TTrainController::RebuildOpTimeToActMultimap
void RebuildOpTimeToActMultimap(int Caller)
new v2.2.0 for OperatorActionPanel (OperatorActionPanel changed for ActionsDueForm at v2....
Definition: TrainUnit.cpp:22173
TTrainController::PwrHigh
bool PwrHigh
Definition: TrainUnit.h:851
TTrainController::BuildContinuationTrainExpectationMultiMap
void BuildContinuationTrainExpectationMultiMap(int Caller)
populate the ContinuationTrainExpectationMultiMap during timetable loading
Definition: TrainUnit.cpp:17649
TFixedTrackPiece::GraphicPtr
Graphics::TBitmap * GraphicPtr
the track bitmap for display on the zoomed-in railway
Definition: TrackUnit.h:92
SignallerMoveForwards
@ SignallerMoveForwards
Definition: TrainUnit.h:56
TRailGraphics::CodeR
Graphics::TBitmap * CodeR
Definition: GraphicUnit.h:1017
Create
@ Create
Definition: TrainUnit.h:55
TTrainController::TContinuationEntryVecPosVector
std::vector< int > TContinuationEntryVecPosVector
ensures only one train displayed for a given continuation
Definition: TrainUnit.h:828
TAllRoutes::SetAllRearwardsSignals
void SetAllRearwardsSignals(int Caller, int Attribute, int RouteNumber, int RouteStartPosition)
Set rearwards signals from the specified route starting position.
Definition: TrackUnit.cpp:20928
Arrive
@ Arrive
Definition: TrainUnit.h:55
ChangeDirection
@ ChangeDirection
Definition: TrainUnit.h:55
TTrain::ZeroPowerNoCDTMessage
bool ZeroPowerNoCDTMessage
Definition: TrainUnit.h:353
TTrain::CallingOnFlag
bool CallingOnFlag
calling on permitted
Definition: TrainUnit.h:395
Depart
@ Depart
Definition: TrainUnit.h:55
TRailGraphics::gl89set
Graphics::TBitmap * gl89set
Definition: GraphicUnit.h:721
TTrainController::CheckHeadCodeValidity
bool CheckHeadCodeValidity(int Caller, bool GiveMessages, AnsiString HeadCode)
Returns true if the headcode complies with requirements.
Definition: TrainUnit.cpp:12776
TRailGraphics::gl88set
Graphics::TBitmap * gl88set
Definition: GraphicUnit.h:719
PerfLogForm
TPerfLogForm * PerfLogForm
Definition: PerfLogUnit.cpp:11
clBufferStopBackground
#define clBufferStopBackground
Definition: GraphicUnit.h:292
TTrain::MaxRunningSpeed
double MaxRunningSpeed
the current maximum train running speed
Definition: TrainUnit.h:439
TTrack::BarriersDownVector
TActiveLCVector BarriersDownVector
vector of LCs with barriers down
Definition: TrackUnit.h:811
ChangeMaxSpeed
@ ChangeMaxSpeed
Definition: TrainUnit.h:58
TPrefDirElement::GetXLinkPos
int GetXLinkPos() const
Returns the XLink array position.
Definition: TrackUnit.h:287
TTrack::IsLCBarrierDownAtHV
bool IsLCBarrierDownAtHV(int Caller, int HLoc, int VLoc)
True if an open (to trains) level crossing is found at H & V.
Definition: TrackUnit.cpp:7512
RestoreTimetableControl
@ RestoreTimetableControl
Definition: TrainUnit.h:56
FailCrashed
@ FailCrashed
Definition: TrainUnit.h:42
TRailGraphics::TempHeadCode
Graphics::TBitmap * TempHeadCode
Definition: GraphicUnit.h:910
TAllRoutes::AutoSigsRoute
@ AutoSigsRoute
Definition: TrackUnit.h:1681
IntermediateSequence
@ IntermediateSequence
Definition: TrainUnit.h:81
TTrack::TSigElement::Attribute
int Attribute
the signal state - red, yellow, double yellow or green
Definition: TrackUnit.h:730
TActionVectorEntry::LocationType
TTimetableLocationType LocationType
indicates where the train is when the relevant action occurs
Definition: TrainUnit.h:143
TTrack::GapFlashGreenPosition
int GapFlashGreenPosition
Definition: TrackUnit.h:793
TAllRoutes::TRouteElementPair
std::pair< int, unsigned int > TRouteElementPair
defines a specific element in a route, the first (int) value is the vector position in the AllRoutesV...
Definition: TrackUnit.h:1693
NamedNonStationLocation
@ NamedNonStationLocation
Definition: TrackUnit.h:67
FinRemHere
@ FinRemHere
Definition: TrainUnit.h:70
TTrain::HeadCodeGrPtr
Graphics::TBitmap * HeadCodeGrPtr[4]
points to the headcode segment graphics e.g. 5,A,4,7.
Definition: TrainUnit.h:526
TTrainController::TContinuationTrainExpectationEntry::TrainDataEntryPtr
TTrainDataEntry * TrainDataEntryPtr
points to the service entry in the timetable's TrainDataVector
Definition: TrainUnit.h:789
FSHNewService
@ FSHNewService
Definition: TrainUnit.h:71
clB3G5R5
#define clB3G5R5
Definition: GraphicUnit.h:203
TTrain::CoastingBrakeRate
double CoastingBrakeRate
the train brake rate when coasting
Definition: TrainUnit.h:447
TTrain::MaxBrakeRate
double MaxBrakeRate
the maximum brake rate that the train can achieve
Definition: TrainUnit.h:443
TTimetableLocationType
TTimetableLocationType
Definition: TrainUnit.h:75
TAllRoutes::DiagonalFouledByRoute
bool DiagonalFouledByRoute(int Caller, int HLoc, int VLoc, int DiagonalLinkNumber)
As above but only checks for a route (may or may not be a train present (new at v1....
Definition: TrackUnit.cpp:21689
TTrainController::CheckForDuplicateCrossReferences
bool CheckForDuplicateCrossReferences(int Caller, AnsiString MainHeadCode, AnsiString SecondHeadCode, bool GiveMessages)
A timetable validation function where referenced services are checked for uniqueness,...
Definition: TrainUnit.cpp:15417
TTrackElement::StationEntryStopLinkPos2
int StationEntryStopLinkPos2
Definition: TrackUnit.h:153
TTrain::MidExitPos
int MidExitPos
Definition: TrainUnit.h:377
TRailGraphics::CodeD
Graphics::TBitmap * CodeD
Definition: GraphicUnit.h:1003
TTrain::TrainFailed
bool TrainFailed
Definition: TrainUnit.h:423
TUtilities::CheckFileStringZeroDelimiter
bool CheckFileStringZeroDelimiter(std::ifstream &InFile)
checks that the value is a string ('0' only accepted as the delimiter), returns true for success
Definition: Utilities.cpp:435
TTrack::TTrackVectorIterator
std::vector< TTrackElement >::iterator TTrackVectorIterator
iterator for TTrackVector
Definition: TrackUnit.h:652
TRailGraphics::Code_w
Graphics::TBitmap * Code_w
Definition: GraphicUnit.h:986
FailDerailed
@ FailDerailed
Definition: TrainUnit.h:42
TTrainController::SPADWarning
bool SPADWarning
Definition: TrainUnit.h:837
TTrain::ZeroPowerNoFrontSplitMessage
bool ZeroPowerNoFrontSplitMessage
Definition: TrainUnit.h:349
TRailGraphics::Code_s
Graphics::TBitmap * Code_s
Definition: GraphicUnit.h:982
TTrainController::GetExitLocationAndAt
AnsiString GetExitLocationAndAt(int Caller, TNumList &ExitList, AnsiString &AllowedExits) const
Check all timetable names in ExitList, if all same return " at [name]" + AllowableExits = elements,...
Definition: TrainUnit.cpp:21461
TTrain::SaveOneSessionTrain
void SaveOneSessionTrain(int Caller, std::ofstream &OutFile)
Data for a single train is saved to a session file.
Definition: TrainUnit.cpp:8442
TTrain::DepartureTimeSet
bool DepartureTimeSet
set when stopped at a location and the next action is departure (set in UpdateTrain when ReleaseTime ...
Definition: TrainUnit.h:397
TTrain::Plotted
bool Plotted
set when train plotted on screen
Definition: TrainUnit.h:498
TTrain::LagElement
int LagElement
Definition: TrainUnit.h:377
TTrain::RemainHere
void RemainHere(int Caller)
Sends the 'train terminated' message to the performance log and sets TimetableFinished to true.
Definition: TrainUnit.cpp:6736
TTrackElement::StationEntryStopLinkPos3
int StationEntryStopLinkPos3
Definition: TrackUnit.h:153
TTrainController::BufferAttentionWarning
bool BufferAttentionWarning
Definition: TrainUnit.h:837
TTrainController::TrainVectorAtIdent
TTrain & TrainVectorAtIdent(int Caller, int TrainID)
Return a reference to the train with ID TrainID, carries out validity checking on TrainID.
Definition: TrainUnit.cpp:10888
TPrefDirElement::GetRouteEXGraphicPtr
Graphics::TBitmap * GetRouteEXGraphicPtr()
Returns route graphic.
Definition: TrackUnit.h:323
TRailGraphics::smLightBlue
Graphics::TBitmap * smLightBlue
Definition: GraphicUnit.h:901
TTrainController::TContinuationAutoSigEntry::RouteNumber
int RouteNumber
the AllRoutesVector position of the route
Definition: TrainUnit.h:763
TTrainController::StopTTClockMessage
void StopTTClockMessage(int Caller, AnsiString Message)
sends a message to the user and stops the timetable clock while it is displayed
Definition: TrainUnit.cpp:17380
TExitInfo::TimeToExitSecs
short TimeToExitSecs
Definition: TrainUnit.h:111
TTrainController::OperatingTrainLateArr
int OperatingTrainLateArr
< all these set to 0 in constructor
Definition: TrainUnit.h:897
TRailGraphics::CodeJ
Graphics::TBitmap * CodeJ
Definition: GraphicUnit.h:1009
TUtilities::IncrementAnsiTimeOneMinute
AnsiString IncrementAnsiTimeOneMinute(AnsiString TimeVal)
takes "HH:MM" and increments it to "HH:MX", where MX == MM + 1, incrementing the hour if necessary
Definition: Utilities.cpp:826
TRailGraphics::CodeQ
Graphics::TBitmap * CodeQ
Definition: GraphicUnit.h:1016
TDisplay::GetOutputLog9
TLabel * GetOutputLog9()
Definition: DisplayUnit.h:185
TAllRoutes::RemoveRouteElement
void RemoveRouteElement(int Caller, int HLoc, int VLoc, int ELink)
Erases the route element from Route2MultiMap and from the PrefDirVector.
Definition: TrackUnit.cpp:20717
TTrainController::CrashWarning
bool CrashWarning
Definition: TrainUnit.h:837
TTrainController::CheckLocationValidity
bool CheckLocationValidity(int Caller, AnsiString LocStr, bool GiveMessages, bool CheckLocationsExistInRailway)
Returns true if the location name complies with requirements.
Definition: TrainUnit.cpp:12726
TAllRoutes::TLockedRouteClass::LastXLinkPos
int LastXLinkPos
the XLinkPos value of the last (i.e. most forward) element in the route
Definition: TrackUnit.h:1673
TTrain::PlotTrain
void PlotTrain(int Caller, TDisplay *Disp)
Plots the train on the display in normal (zoomed-in) mode.
Definition: TrainUnit.cpp:9538
TNumListIterator
TNumList::iterator TNumListIterator
Definition: TrainUnit.h:99
FailLevelCrossingCrash
@ FailLevelCrossingCrash
Definition: TrainUnit.h:44
TActionVectorEntry::DepartureTime
TDateTime DepartureTime
relevant times at which the action is timetabled, zeroed on creation so change to -1 as a marker for ...
Definition: TrainUnit.h:137
TRailGraphics::Code_f
Graphics::TBitmap * Code_f
Definition: GraphicUnit.h:969
FailCreateLockedRoute
@ FailCreateLockedRoute
Definition: TrainUnit.h:43
TTrainController::TContinuationTrainExpectationEntry::IncrementalDigits
int IncrementalDigits
Repeat headcode separation.
Definition: TrainUnit.h:785
TTrainController::SaveSessionTrains
void SaveSessionTrains(int Caller, std::ofstream &SessionFile)
save trains to a session file
Definition: TrainUnit.cpp:17395
TOneCompleteFormattedTrain::HeadCode
AnsiString HeadCode
Definition: TrainUnit.h:271
TRailGraphics::SetWebSafePalette
void SetWebSafePalette(int Caller, Graphics::TBitmap *bmp)
Definition: GraphicUnit.cpp:3473
TRailGraphics::Code_d
Graphics::TBitmap * Code_d
Definition: GraphicUnit.h:967
TTrain::JoinedBy
void JoinedBy(int Caller)
Carry out the actions needed when a train is waiting to be joined by another train.
Definition: TrainUnit.cpp:6519
FailIncorrectExit
@ FailIncorrectExit
Definition: TrainUnit.h:44
TRailGraphics::Code_t
Graphics::TBitmap * Code_t
Definition: GraphicUnit.h:983
TTrainController::GetServiceFromVector
TTrainDataEntry GetServiceFromVector(AnsiString Caller, AnsiString HeadCode, TTrainDataVector Vector, bool &FinishType, bool &FoundFlag)
Return the TrainDataVector entry corresponding to ServiceReference, FinishType is 0 for end of servic...
Definition: TrainUnit.cpp:20817
TRailGraphics::ChangeBackgroundColour
void ChangeBackgroundColour(int Caller, Graphics::TBitmap *BitmapIn, Graphics::TBitmap *BitmapOut, TColor NewBackgroundColour, TColor OldBackgroundColour, bool &ColourError)
Definition: GraphicUnit.cpp:3724
TTrain::GetLeadElement
int GetLeadElement()
get LeadElement - used in RouteLockingRequired in TrackUnit.cpp
Definition: TrainUnit.h:714
TimeCmd
@ TimeCmd
Definition: TrainUnit.h:70
TTrain::BackgroundPtr
Graphics::TBitmap * BackgroundPtr[4]
the existing track graphic that the train headcode segment covers up (one for each headcode segment)
Definition: TrainUnit.h:522
TUtilities::LoadFileDouble
double LoadFileDouble(std::ifstream &InFile)
loads a double value from the file (converts from a string to a double) and uses the local decimal po...
Definition: Utilities.cpp:172
TTrain::ZeroPowerDepartMessage
bool ZeroPowerDepartMessage
Definition: TrainUnit.h:358
TTrain::DistanceToStationStop
int DistanceToStationStop
calculated in UpdateTrain & used in CalcDistanceToRedSignalandStopTime to cater for trains running ea...
Definition: TrainUnit.h:473
TTrainController::EntryPos
int EntryPos(int Caller, int TrainIDIn, int TrackVectorNumber)
Return the track entry link (Link[]) array position for the given train on track element at track vec...
Definition: TrainUnit.cpp:10850
SignallerLeave
@ SignallerLeave
Definition: TrainUnit.h:57
TTrain::PlotEntryPos
int PlotEntryPos[4]
the LinkPos value corresponding to the train entry link of the element where each of the 4 headcode c...
Definition: TrainUnit.h:515
NotStarted
@ NotStarted
Definition: TrainUnit.h:92
TTrack::OneNamedLocationElementAtLocation
bool OneNamedLocationElementAtLocation(int Caller, AnsiString LocationName)
True if there is at least one named location element with name 'LocationName', used in timetable inte...
Definition: TrackUnit.cpp:11810
TTrainController::SPADEvents
int SPADEvents
Definition: TrainUnit.h:891
TTrainController::LastTTTime
AnsiString LastTTTime
Stores the last time used in the timetable as an AnsiString - used for timetable analysis.
Definition: TrainUnit.h:831
TTrack::OneStationLongEnoughForSplit
bool OneStationLongEnoughForSplit(int Caller, AnsiString LocationName)
Definition: TrackUnit.cpp:11284
TTrain::ZeroPowerNoRepeatShuttleMessage
bool ZeroPowerNoRepeatShuttleMessage
Definition: TrainUnit.h:356
TTrackElement::SigAspect
enum TTrackElement::@1 SigAspect
TRailGraphics::gl90set
Graphics::TBitmap * gl90set
Definition: GraphicUnit.h:724
TTrain::ActionVectorEntryPtr
TActionVectorEntry * ActionVectorEntryPtr
points to the current position in the ActionVector (a member of the TTrainDataEntry class)
Definition: TrainUnit.h:383
TTrainDataEntry
Contains all data for a single timetable service entry.
Definition: TrainUnit.h:211
LeadMid
@ LeadMid
Definition: TrainUnit.h:304
FailMissedPass
@ FailMissedPass
Definition: TrainUnit.h:43
clSignalStopBackground
#define clSignalStopBackground
Definition: GraphicUnit.h:300
TTrainController::LateDeps
int LateDeps
Definition: TrainUnit.h:881
TAllRoutes::IsElementInLockedRouteGetPrefDirElementGetLockedVectorNumber
bool IsElementInLockedRouteGetPrefDirElementGetLockedVectorNumber(int Caller, int TrackVectorPosition, int XLinkPos, TPrefDirElement &PrefDirElement, int &LockedVectorNumber)
Checks whether the preferred direction element at TrackVectorPosition with XLinkPos value is in a loc...
Definition: TrackUnit.cpp:21202
TrackUnit.h
TTrainController::CheckStartAllowable
bool CheckStartAllowable(int Caller, int RearPosition, int RearExitPos, AnsiString HeadCode, bool ReportFlag, TActionEventType &EventType)
Called when trying to introduce a new train - checks for points in correct orientation,...
Definition: TrainUnit.cpp:16268
TTrack::ResetAllTrainIDsAndFailedPointOrigSpeedLimits
void ResetAllTrainIDsAndFailedPointOrigSpeedLimits(int Caller)
Definition: TrackUnit.cpp:7884
TTrainController::RebuildTimeToExitMultiMap
void RebuildTimeToExitMultiMap(int Caller)
new for multiplayer
Definition: TrainUnit.cpp:22344
TRailGraphics::Code_r
Graphics::TBitmap * Code_r
Definition: GraphicUnit.h:981
TTrain::SendMissedActionLogs
void SendMissedActionLogs(int Caller, int IncNum, TActionVectorEntry *Ptr)
Missed actions (see NameInTimetableBeforeCDT above) sent to the performance log and performance file.
Definition: TrainUnit.cpp:6774
TOneRoute::RouteID
int RouteID
the ID number of the route, this is needed for session saves
Definition: TrackUnit.h:1568
TTrainController::TContinuationTrainExpectationEntry::RepeatNumber
int RepeatNumber
service RepeatNumber
Definition: TrainUnit.h:781
TRailGraphics::CodeX
Graphics::TBitmap * CodeX
Definition: GraphicUnit.h:1023
TOneTrainFormattedEntry::Time
AnsiString Time
the time of the action as a string
Definition: TrainUnit.h:258
TRailGraphics::CodeT
Graphics::TBitmap * CodeT
Definition: GraphicUnit.h:1019
TTrainDataEntry::PowerAtRail
double PowerAtRail
in Watts (taken as 80% of the train's Gross Power, i.e. that entered by the user)
Definition: TrainUnit.h:221
TTrainController::LastTrainLoaded
int LastTrainLoaded
displays last train loaded from session file, used for debugging
Definition: TrainUnit.h:901
TTrainController::NotStartedTrainLateMins
float NotStartedTrainLateMins
total late minutes of trains that haven't started yet on exit operation for locations not reached yet
Definition: TrainUnit.h:858
Utilities.h
TTrainController::TContinuationAutoSigVectorIterator
TContinuationAutoSigVector::iterator TContinuationAutoSigVectorIterator
Definition: TrainUnit.h:771
TTrain::SkippedDeparture
bool SkippedDeparture
< used for terminating a service early and becoming new follow-on service
Definition: TrainUnit.h:337
TTrainController::BgndColNumber
int BgndColNumber
8 bit websafe colour number corresponding to background colour (Utilities->clTransparent)
Definition: TrainUnit.h:927
TTrain::TrainOnContinuation
bool TrainOnContinuation(int Caller)
Returns true if any part of train on a continuation - called when checking for failures,...
Definition: TrainUnit.cpp:10020
ChangeDescription
@ ChangeDescription
Definition: TrainUnit.h:58
TTrainController::CheckSessionLockedRoutes
bool CheckSessionLockedRoutes(int Caller, std::ifstream &SessionFile)
Part of the session file integrity check for locked routes, true for success.
Definition: TrainUnit.cpp:17498
FailTrainEntry
@ FailTrainEntry
Definition: TrainUnit.h:41
MidLag
@ MidLag
Definition: TrainUnit.h:304
TDisplay::Update
void Update()
Repaint the screen display.
Definition: DisplayUnit.h:222
TTrainController::LocServiceTimesDepTimeSort
bool LocServiceTimesDepTimeSort(TLocServiceTimes i, TLocServiceTimes j)
Definition: TrainUnit.h:947
TTrainController::CheckSessionTrains
bool CheckSessionTrains(int Caller, std::ifstream &InFile)
Part of the session file integrity check for train entries, true for success.
Definition: TrainUnit.cpp:17436
StartNew
@ StartNew
Definition: TrainUnit.h:70
TakeSignallerControl
@ TakeSignallerControl
Definition: TrainUnit.h:55
LeadMidLag
@ LeadMidLag
Definition: TrainUnit.h:304
TTrack::GapFlashGreen
TGraphicElement * GapFlashGreen
Definition: TrackUnit.h:815
TTrain::ZeroPowerNoRepeatShuttleOrNewServiceMessage
bool ZeroPowerNoRepeatShuttleOrNewServiceMessage
Definition: TrainUnit.h:357
TrainFailure
@ TrainFailure
Definition: TrainUnit.h:56
TTrain::MinsDelayed
float MinsDelayed
new at v2.2.0 for operator time to act panel. Calculated in UpdateTrain
Definition: TrainUnit.h:461
TTrack::TSigElement::SigPtr
Graphics::TBitmap * SigPtr
pointer to the graphic
Definition: TrackUnit.h:732
TTrainController::TimeToExitMultiMap
TTimeToExitMultiMap TimeToExitMultiMap
Map of times to exit & exit coordinates.
Definition: TrainUnit.h:1053
TAllRoutes::FindRouteNumberFromRoute2MultiMapNoErrors
bool FindRouteNumberFromRoute2MultiMapNoErrors(int Caller, int HLoc, int VLoc, int ELink, int &RouteNumber)
If a route is present at H, V & Elink returns true with RouteNumber giving vector position in AllRout...
Definition: TrackUnit.cpp:20465
TTrainController::Last2CharactersBothDigits
bool Last2CharactersBothDigits(int Caller, AnsiString HeadCode)
Checks the last two characters in HeadCode and returns true if both are digits.
Definition: TrainUnit.cpp:12212
TRailGraphics::Code2
Graphics::TBitmap * Code2
Definition: GraphicUnit.h:992
TTrack::GetVectorPositionsFromInactiveTrackMap
TIMPair GetVectorPositionsFromInactiveTrackMap(int Caller, int HLoc, int VLoc, bool &FoundFlag)
Similar to GetVectorPositionFromTrackMap but for inactive elements, a pair is returned because there ...
Definition: TrackUnit.cpp:5992
TTrain::BackgroundColour
TColor BackgroundColour
< Used for writing to operating image when long refs showing
Definition: TrainUnit.h:535
TTrainOperatingData::EventReported
TActionEventType EventReported
Definition: TrainUnit.h:190
TTrain
Definition: TrainUnit.h:310
TRailGraphics::Code_z
Graphics::TBitmap * Code_z
Definition: GraphicUnit.h:989
TTrainController::CheckTimeValidity
bool CheckTimeValidity(int Caller, AnsiString TimeStr, TDateTime &Time)
returns true if the time complies with requirements
Definition: TrainUnit.cpp:12231
clSignallerStopped
#define clSignallerStopped
Definition: GraphicUnit.h:299
TOnePrefDir::GetFixedPrefDirElementAt
const TPrefDirElement & GetFixedPrefDirElementAt(int Caller, int At) const
Return a non-modifiable element at PrefDirVector position 'At'.
Definition: TrackUnit.cpp:12546
FailBuffersPreventingStart
@ FailBuffersPreventingStart
Definition: TrainUnit.h:44
TTrainController::LocServiceTimesArrTimeSort
bool LocServiceTimesArrTimeSort(TLocServiceTimes i, TLocServiceTimes j)
Definition: TrainUnit.h:942
TOneTrainFormattedEntry::Action
AnsiString Action
includes location if relevant
Definition: TrainUnit.h:256
TTrainController::ContinuationTrainExpectationMultiMap
TContinuationTrainExpectationMultiMap ContinuationTrainExpectationMultiMap
Multimap for TContinuationTrainExpectationEntry objects, the access key is the expectation time.
Definition: TrainUnit.h:915
TPerfLogForm::PerformanceLog
void PerformanceLog(int Caller, AnsiString Statement)
Send Statement to the performance log on screen and to the file.
Definition: PerfLogUnit.cpp:32
TTrain::StoppedForTrainInFront
bool StoppedForTrainInFront
Definition: TrainUnit.h:504
GapJump
@ GapJump
Definition: TrackUnit.h:66
TRailGraphics::CodeK
Graphics::TBitmap * CodeK
Definition: GraphicUnit.h:1010
TTrainController::MissedStops
int MissedStops
Definition: TrainUnit.h:884
NoSequence
@ NoSequence
Definition: TrainUnit.h:81
clCallOnBackground
#define clCallOnBackground
Definition: GraphicUnit.h:293
TTrackElement::Length01
int Length01
Definition: TrackUnit.h:151
TTrackElement::SpeedLimit01
int SpeedLimit01
Definition: TrackUnit.h:151
TTrain::TerminatedMessageSent
bool TerminatedMessageSent
set when a 'train terminated' message has been logged, to prevent its being logged more than once
Definition: TrainUnit.h:419
TTrain::FinishJoin
void FinishJoin(int Caller)
Carry out the actions needed when a train is waiting to join another train.
Definition: TrainUnit.cpp:6470
TTrainController::FinishedOperation
void FinishedOperation(int Caller)
called when exiting operation mode to delete all trains and timetable data etc
Definition: TrainUnit.cpp:10481
TDisplay::DisplayOffsetV
static int DisplayOffsetV
the vertical offset of the displayed screen (as viewpoint moves down [railway moves up] this offset i...
Definition: DisplayUnit.h:79
TTrain::MidElement
int MidElement
Definition: TrainUnit.h:377
TAllRoutes::CallonVector
std::vector< TCallonEntry > CallonVector
the store of all call-on entries
Definition: TrackUnit.h:1720
TTrain::LeadElement
int LeadElement
Definition: TrainUnit.h:377
clDerailedBackground
#define clDerailedBackground
Definition: GraphicUnit.h:295
TTrain::RepeatNumber
int RepeatNumber
indicates which of the repeating services this train represents (0 = first service)
Definition: TrainUnit.h:371
TTrainDataEntry::ActionVector
TActionVector ActionVector
all the actions for the train
Definition: TrainUnit.h:231
TTrainController::BaseTime
TDateTime BaseTime
CurrentDateTime (i.e. real time) when operation restarts after a pause.
Definition: TrainUnit.h:744
TTrain::StartSpeed
int StartSpeed
the speed of the train when introduced into the railway (in km/h)
Definition: TrainUnit.h:375
TExitInfo::TExitInfo
TExitInfo()
Definition: TrainUnit.cpp:64
TTrainController::LoadSessionLockedRoutes
void LoadSessionLockedRoutes(int Caller, std::ifstream &SessionFile)
load locked routes from a session file
Definition: TrainUnit.cpp:17477
TTrainController::EarlyPasses
int EarlyPasses
Definition: TrainUnit.h:877
TTrain::HeadCode
AnsiString HeadCode
Definition: TrainUnit.h:333
TDisplay::GetOutputLog7
TLabel * GetOutputLog7()
Definition: DisplayUnit.h:175
TTrain::TrainInFront
bool TrainInFront
Definition: TrainUnit.h:505
TTrainController::TOpTimeToActMultiMapEntry
std::pair< float, THCandTrainPosParam > TOpTimeToActMultiMapEntry
Definition: TrainUnit.h:826
FailMissedSplit
@ FailMissedSplit
Definition: TrainUnit.h:42
TTrain::EntryTime
TDateTime EntryTime
Definition: TrainUnit.h:477
TExitInfo::RepeatNumber
short RepeatNumber
Definition: TrainUnit.h:110
TTrainController::TContinuationAutoSigEntry::FirstDelay
double FirstDelay
Definition: TrainUnit.h:759
TTrain::LastSigPassedFailed
bool LastSigPassedFailed
flag used to erase route elements in an autosigs route after a failed signal
Definition: TrainUnit.h:405
TTrainController::SaveTrainDataVectorToFile
void SaveTrainDataVectorToFile(int Caller)
diagnostic function to store all train data to a file for examination, not used normally
Definition: TrainUnit.cpp:17200
TTrainController::NearTransparentColNumber
int NearTransparentColNumber
Definition: TrainUnit.h:931
TOnePrefDir::PrefDirSize
unsigned int PrefDirSize() const
Return the vector size.
Definition: TrackUnit.h:1426
TTrack::PlotSignal
void PlotSignal(int Caller, TTrackElement TrackElement, TDisplay *Disp)
Plot signals on screen according to their aspect (Attribute value)
Definition: TrackUnit.cpp:6280
End
@ End
Definition: TrackUnit.h:76
TUtilities::FixedMinRepairTime
int FixedMinRepairTime
Definition: Utilities.h:72
TTrain::OneLengthAccelDecel
bool OneLengthAccelDecel
set when a train can only move forwards one element before stopping but needs to accelerate for the f...
Definition: TrainUnit.h:409
TTrain::FloatingLabelNextString
AnsiString FloatingLabelNextString(int Caller, TActionVectorEntry *Ptr)
Used in the floating window to display the 'Next' action.
Definition: TrainUnit.cpp:7428
TTrack::TimetabledLocationNameAllocated
bool TimetabledLocationNameAllocated(int Caller, AnsiString LocationName)
True if a non-empty LocationName found as a timetabled location name i.e. not as a continuation name.
Definition: TrackUnit.cpp:9086
StaticFeaturesDisplay
TDisplay * StaticFeaturesDisplay
The object pointer for track, text & graphics only for replacing the text of long service references.
Definition: DisplayUnit.cpp:56
TTrain::FailedTrainNoFinishJoinMessage
bool FailedTrainNoFinishJoinMessage
Definition: TrainUnit.h:351
TTrain::EnterLongServRefAsName
void EnterLongServRefAsName(int Caller, TDisplay *Disp)
This is to display the train's service ref above the train.
Definition: TrainUnit.cpp:2479
TDisplay::GetImage
TImage * GetImage()
Return a pointer to the screen image.
Definition: DisplayUnit.h:139
TTrack::InactiveTrackElementAt
TTrackElement & InactiveTrackElementAt(int Caller, int At)
A range-checked version of InactiveTrackVector.at(At)
Definition: TrackUnit.cpp:11249
ExitRailway
@ ExitRailway
Definition: TrainUnit.h:71
TPrefDirElement::GetTrackVectorPosition
unsigned int GetTrackVectorPosition() const
Returns TrackVectorPosition.
Definition: TrackUnit.h:305
TTrain::StationStopCalculated
bool StationStopCalculated
used in calculating DistanceToStationStop for trains running early before they have reached the stop ...
Definition: TrainUnit.h:415
TTrainController::LoadSessionContinuationAutoSigEntries
void LoadSessionContinuationAutoSigEntries(int Caller, std::ifstream &SessionFile)
load ContinuationAutoSigEntries from a session file
Definition: TrainUnit.cpp:17560
FailMissedDSC
@ FailMissedDSC
Definition: TrainUnit.h:42
TTrackElement
Basic track elements as implemented in the overall railway layout.
Definition: TrackUnit.h:125
TRailGraphics::Code_n
Graphics::TBitmap * Code_n
Definition: GraphicUnit.h:977
FailMissedNewService
@ FailMissedNewService
Definition: TrainUnit.h:43
TUtilities::Format96HHMMSS
AnsiString Format96HHMMSS(TDateTime DateTime)
formats a TDateTime into an AnsiString of the form hh:mm:ss where hh runs from 00 to 95 & resets when...
Definition: Utilities.cpp:788
TRailGraphics::smSolidBgnd
Graphics::TBitmap * smSolidBgnd
Definition: GraphicUnit.h:1027
clTRSBackground
#define clTRSBackground
Definition: GraphicUnit.h:304
TTrainController::OtherMissedEvents
int OtherMissedEvents
Definition: TrainUnit.h:889
TTrainController::EarlyExits
int EarlyExits
Definition: TrainUnit.h:878
TTrain::ExitSpeedHalf
double ExitSpeedHalf
speed when half way into the next element
Definition: TrainUnit.h:433
SignalPost
@ SignalPost
Definition: TrackUnit.h:66
TRailGraphics::Code_h
Graphics::TBitmap * Code_h
Definition: GraphicUnit.h:971
TTrain::LastActionTime
TDateTime LastActionTime
time of the last timetabled event, used to ensure at least a 30 second delay before the next action
Definition: TrainUnit.h:481
TDisplay::GetOutputLog1
TLabel * GetOutputLog1()
Return pointers to warning message logs (appear above the railway display during operation)
Definition: DisplayUnit.h:144
TAllRoutes::TRoute2MultiMapIterator
TRoute2MultiMap::iterator TRoute2MultiMapIterator
Definition: TrackUnit.h:1697
TTrain::TrainFailurePending
bool TrainFailurePending
set when failure due & takes effect when all PlotElements properly set, added at v2....
Definition: TrainUnit.h:361
FailMissedTerminate
@ FailMissedTerminate
Definition: TrainUnit.h:43
TTrainController::SecondPassActions
bool SecondPassActions(int Caller, bool GiveMessages, bool &TwoLocationFlag)
Carry out further detailed timetable consistency checks, return true for success.
Definition: TrainUnit.cpp:13413
TRailGraphics::Code9
Graphics::TBitmap * Code9
Definition: GraphicUnit.h:999
TTrainDataEntry::HeadCode
AnsiString HeadCode
Definition: TrainUnit.h:213
FailTrainInFront
@ FailTrainInFront
Definition: TrainUnit.h:45
TTrain::TrainHasFailed
void TrainHasFailed(int Caller)
Called when there is a random train failure.
Definition: TrainUnit.cpp:6098
TTrain::LongServRefWorkingBitmap
Graphics::TBitmap * LongServRefWorkingBitmap
< Stores the long service ref name for > 4 chars
Definition: TrainUnit.h:530
TUtilities::MinorDelayFactor
float MinorDelayFactor
Definition: Utilities.h:53
TTrain::LongServRefEnteredFlag
bool LongServRefEnteredFlag
defines whether service ref plotted or not
Definition: TrainUnit.h:488
clNormalBackground
#define clNormalBackground
Definition: GraphicUnit.h:298
TTrainController::CheckNonRepeatingShuttleLinksAndSetData
bool CheckNonRepeatingShuttleLinksAndSetData(int Caller, AnsiString MainHeadCode, AnsiString NonRepeatingHeadCode, bool SetDataAndCheckLocations, bool GiveMessages)
A timetable validation function where cross references are checked for validity for non-repeating shu...
Definition: TrainUnit.cpp:16545
TTrainController::CheckFourthValidityForSplit
bool CheckFourthValidityForSplit(AnsiString SplitDistributionString, bool GiveMessages)
Checks fourth segment in timetable for train splits - percentage mass then '-' then percentage power ...
Definition: TrainUnit.cpp:12681
TActionVectorEntry::NonRepeatingShuttleLinkEntryPtr
TTrainDataEntry * NonRepeatingShuttleLinkEntryPtr
pointer used by shuttles for the non-shuttle train links, in & out, the corresponding non-shuttle lin...
Definition: TrainUnit.h:151
TTrainController::SPADRisks
int SPADRisks
Definition: TrainUnit.h:892
TTrainController::TLocServiceTimesVector
std::vector< TLocServiceTimes > TLocServiceTimesVector
Definition: TrainUnit.h:812
TTrainController::LocServiceTimesAtLocTimeSort
bool LocServiceTimesAtLocTimeSort(TLocServiceTimes i, TLocServiceTimes j)
Definition: TrainUnit.h:951
TTrain::EntrySpeed
double EntrySpeed
speed at which the train enters the next element
Definition: TrainUnit.h:431
TConfiguration
TConfiguration
< describes the type of track link. 'End' is used for both buffer stop and continuation entry/exit po...
Definition: TrackUnit.h:75
TTrain::TrainInFrontMessage
bool TrainInFrontMessage
flags to indicate whether the respective message has been sent
Definition: TrainUnit.h:359
TTrain::TrainGone
bool TrainGone
set when train has left the railway, so it can be removed from the display at the next clock tick
Definition: TrainUnit.h:500
TTrackType
TTrackType
< describes the type of track element
Definition: TrackUnit.h:65
TTrainController::OnTimePasses
int OnTimePasses
Definition: TrainUnit.h:887
TTrainController::SigSHigh
bool SigSHigh
Definition: TrainUnit.h:851
TRailGraphics::smCaramel
Graphics::TBitmap * smCaramel
Definition: GraphicUnit.h:898
TGraphicElement::PlotOriginal
void PlotOriginal(int Caller, TDisplay *Disp)
Plot the original graphic on screen.
Definition: TrackUnit.cpp:1928
TExitInfo
Definition: TrainUnit.h:107
clBufferAttentionNeeded
#define clBufferAttentionNeeded
Definition: GraphicUnit.h:291
TUtilities::ModerateDelayCutoff
float ModerateDelayCutoff
Definition: Utilities.h:51
TTrain::StoppedAfterSPAD
bool StoppedAfterSPAD
Definition: TrainUnit.h:504
FailSPAD
@ FailSPAD
Definition: TrainUnit.h:41
TTrainController::OnTimeArrivals
int OnTimeArrivals
Definition: TrainUnit.h:885
clTrainFailedBackground
#define clTrainFailedBackground
Definition: GraphicUnit.h:305
TRailGraphics::Code_x
Graphics::TBitmap * Code_x
Definition: GraphicUnit.h:987
clFrontCodeSignaller
#define clFrontCodeSignaller
Definition: GraphicUnit.h:296
TimeCmdHeadCode
@ TimeCmdHeadCode
Definition: TrainUnit.h:70
Utilities
TUtilities * Utilities
Definition: Utilities.cpp:47
TTrainController::LongServRefFontColNumber
int LongServRefFontColNumber
the websafe colour number for long serv ref names
Definition: TrainUnit.h:929
TFixedTrackPiece::SmallGraphicPtr
Graphics::TBitmap * SmallGraphicPtr
the track bitmap for display on the zoomed-out railway
Definition: TrackUnit.h:94
TActionVectorEntry::ArrivalTime
TDateTime ArrivalTime
Definition: TrainUnit.h:137
TTrain::StoppedAtBuffers
bool StoppedAtBuffers
Definition: TrainUnit.h:504
TTrainController::CreateTTAnalysisFile
bool CreateTTAnalysisFile(int Caller, AnsiString RailwayTitle, AnsiString TimetableTitle, AnsiString CurDir, bool ArrChecked, bool DepChecked, bool AtLocChecked, bool DirChecked, int ArrRange, int DepRange)
Generate a timetable analysis file in the 'Formatted Timetables' folder, return false if failed for a...
Definition: TrainUnit.cpp:18381
TRailGraphics::Code_b
Graphics::TBitmap * Code_b
Definition: GraphicUnit.h:965
TTrain::Mass
int Mass
in kg
Definition: TrainUnit.h:471
TRailGraphics::Code0
Graphics::TBitmap * Code0
Definition: GraphicUnit.h:990
TTrainController::ProcessOneTimetableLine
bool ProcessOneTimetableLine(int Caller, int Count, AnsiString OneLine, bool &EndOfFile, bool FinalCall, bool GiveMessages, bool CheckLocationsExistInRailway)
Carry out preliminary (mainly syntax) validity checks on a single timetable service entry and (if Fin...
Definition: TrainUnit.cpp:11458
TTrainController::TContinuationTrainExpectationEntry::IncrementalMinutes
int IncrementalMinutes
Repeat separation in minutes.
Definition: TrainUnit.h:783
TTrain::LeadExitPos
int LeadExitPos
Definition: TrainUnit.h:377
TTrain::FrontElementLength
int FrontElementLength
values associated with the element immediately in front of the train (speed in km/h,...
Definition: TrainUnit.h:469
TTimetableShuttleLinkType
TTimetableShuttleLinkType
Definition: TrainUnit.h:85
TRailGraphics::CodeP
Graphics::TBitmap * CodeP
Definition: GraphicUnit.h:1015
Pass
@ Pass
Definition: TrainUnit.h:57
TTrain::SkipPtrValue
int SkipPtrValue
stores the pointer increment from first action in ActionVector for skipped actions when a departure i...
Definition: TrainUnit.h:385
TNumList
std::list< int > TNumList
a list of valid train exit TrackVector positions for 'Fer' entries
Definition: TrainUnit.h:95
TTrainController::DerailWarning
bool DerailWarning
Definition: TrainUnit.h:837
TTrainController::TContinuationTrainExpectationMultiMapPair
std::pair< TDateTime, TContinuationTrainExpectationEntry > TContinuationTrainExpectationMultiMapPair
a single multimap entry
Definition: TrainUnit.h:797
TTrain::ImageLongServRefBitmap
Graphics::TBitmap * ImageLongServRefBitmap
< General purpose storage for long serv ref display
Definition: TrainUnit.h:532
RouteForceCancelled
@ RouteForceCancelled
Definition: TrainUnit.h:45
EnRoute
@ EnRoute
Definition: TrainUnit.h:76
TTrain::IsTrainIDOnBridgeTrackPos23
bool IsTrainIDOnBridgeTrackPos23(int Caller, unsigned int TrackVectorPosition)
True if train is on a bridge on trackpos 2 & 3.
Definition: TrainUnit.cpp:3624
TTrack::DiagonalFouledByTrain
bool DiagonalFouledByTrain(int Caller, int HLoc, int VLoc, int DiagonalLinkNumber, int &TrainID)
As DiagonalFouledByRouteOrTrain (in TAllRoutes) but only checks for a train (may or may not be a rout...
Definition: TrackUnit.cpp:12258
TTrainController::TotLateDepMins
float TotLateDepMins
Definition: TrainUnit.h:869
TDisplay::ZoomOutFlag
bool ZoomOutFlag
true when zoomed-out
Definition: DisplayUnit.h:70
TRailGraphics::CodeC
Graphics::TBitmap * CodeC
Definition: GraphicUnit.h:1002
TTrain::PlotElement
int PlotElement[4]
the TrackVectorPosition of the element where each of the 4 headcode characters is plotted (need to be...
Definition: TrainUnit.h:513
TTrainController::TServiceCallingLocsList
std::list< AnsiString > TServiceCallingLocsList
Used in determining train directions in timetable conflict analysis.
Definition: TrainUnit.h:815
TTrainController::TrainVector
TTrainVector TrainVector
vector containing all trains currently in the railway
Definition: TrainUnit.h:923
TUtilities::ModerateDelayFactor
float ModerateDelayFactor
Definition: Utilities.h:54
TUtilities::MajorDelayCutoff
float MajorDelayCutoff
Definition: Utilities.h:52
TRailGraphics::Code_l
Graphics::TBitmap * Code_l
Definition: GraphicUnit.h:975
TTrainController::TContinuationAutoSigEntry
< TTClockTime when last session saved - to prevent display of warning message on exit session if < 5 ...
Definition: TrainUnit.h:757
TUtilities::CallLogPop
void CallLogPop(int Caller)
pops the last entry off the call stack, throws an error if called when empty
Definition: Utilities.cpp:50
TTrain::IncrementalMinutes
int IncrementalMinutes
the number of minutes to increment by in repeat entries
Definition: TrainUnit.h:365
TTrain::MaximumSpeedLimit
static const int MaximumSpeedLimit
Definition: TrainUnit.h:325
TRailGraphics::CodeI
Graphics::TBitmap * CodeI
Definition: GraphicUnit.h:1008
TTrainController::TotEarlyArrMins
float TotEarlyArrMins
values for performance file summary
Definition: TrainUnit.h:864
TTrain::WriteTrainToImage
void WriteTrainToImage(int Caller, Graphics::TBitmap *Bitmap)
Called by TTrainController::WriteTrainsToImage (called by TInterface::SaveOperatingImage1Click) to ad...
Definition: TrainUnit.cpp:9551
TRailGraphics::CodeL
Graphics::TBitmap * CodeL
Definition: GraphicUnit.h:1011
TTrainDataEntry::MaxBrakeRate
double MaxBrakeRate
< true if a description is given for the train, if only headcode given for a follow-on service then f...
Definition: TrainUnit.h:217
FNSNonRepeatToShuttle
@ FNSNonRepeatToShuttle
Definition: TrainUnit.h:70
TDisplay::PlotOutput
void PlotOutput(int Caller, int HPos, int VPos, Graphics::TBitmap *PlotItem)
Plot the graphic at screen position HPos & VPos.
Definition: DisplayUnit.cpp:87
TUtilities::SaveFileBool
void SaveFileBool(std::ofstream &OutFile, bool SaveBool)
stores '1' if the bool is true or '0' if false to the file, then a CR
Definition: Utilities.cpp:108
TTrainDataEntry::SignallerSpeed
int SignallerSpeed
in km/h for use when under signaller control
Definition: TrainUnit.h:227
TTrain::AbleToMove
bool AbleToMove(int Caller)
Indicates that a train is not prevented from moving - used to allow appropriate popup menu options wh...
Definition: TrainUnit.cpp:7277
TTrainController::MTBFHours
double MTBFHours
<Message flags in TT checks to stop being given twice
Definition: TrainUnit.h:856
TTrack::TInfrastructureFailureEntry::TVPos
int TVPos
Definition: TrackUnit.h:715
TOneRoute::ForceCancelRoute
void ForceCancelRoute(int Caller)
Cancel a route immediately if a train occupies it when travelling in the wrong direction (or occupies...
Definition: TrackUnit.cpp:19503
TTrain::TrainMode
TTrainMode TrainMode
mode of operation - either Timetable (running under timetable control) or Signaller (running under si...
Definition: TrainUnit.h:485
TTrainController::CheckNonRepeatingShuttleLinkTime
bool CheckNonRepeatingShuttleLinkTime(int Caller, TDateTime ReverseEventTime, TDateTime ForwardEventTime, int RepeatMins, int RepeatNumber)
The forward train is the finish shuttle entry 'Fns-sh', the reverse (new non-repeating service) time ...
Definition: TrainUnit.cpp:16814
FailNoPowerUnableToDepart
@ FailNoPowerUnableToDepart
Definition: TrainUnit.h:45
TActionVectorEntry::NewMaxSpeed
AnsiString NewMaxSpeed
Definition: TrainUnit.h:125
TUtilities::Format96HHMM
AnsiString Format96HHMM(TDateTime DateTime)
formats a TDateTime into an AnsiString of the form hh:mm where hh runs from 00 to 95 & resets when it...
Definition: Utilities.cpp:807
TTrain::CallOnMaxSpeed
static const int CallOnMaxSpeed
km/h
Definition: TrainUnit.h:317
TTrain::CheckAndCancelRouteForWrongEndEntry
void CheckAndCancelRouteForWrongEndEntry(int Caller, int Element, int EntryPos)
Checks whether Element and EntryPos (where train is about to enter) is on an existing route (or cross...
Definition: TrainUnit.cpp:3832
TRailGraphics::CodeW
Graphics::TBitmap * CodeW
Definition: GraphicUnit.h:1022
TTrain::ReleaseTime
TDateTime ReleaseTime
Definition: TrainUnit.h:479
TTrain::GetTrainHeadCode
AnsiString GetTrainHeadCode(int Caller)
Returns the train headcode, taking account of the RepeatNumber.
Definition: TrainUnit.cpp:5640
TTrain::LowEntryValue
bool LowEntryValue(int EntryLink) const
Returns true if EntryLink is 1, 2, 4 or 7, in these circumstances the front of the train (i....
Definition: TrainUnit.cpp:3145
RearSplit
@ RearSplit
Definition: TrainUnit.h:55
TActionVectorEntry::FrontStartOrRepeatDigits
int FrontStartOrRepeatDigits
dual-purpose variables used for the TrackVectorPositions of the rear and front train starting element...
Definition: TrainUnit.h:135
SignallerControlStop
@ SignallerControlStop
Definition: TrainUnit.h:57
TTrack::TrackVector
TTrackVector TrackVector
Definition: TrackUnit.h:835
TTrain::HasTrainGone
bool HasTrainGone()
Check whether the train has left the railway, so that it can be removed from the display at the next ...
Definition: TrainUnit.h:698
TRailGraphics::Code_i
Graphics::TBitmap * Code_i
Definition: GraphicUnit.h:972
TTrainController::ExcessLCDownMins
float ExcessLCDownMins
total excess time in minutes over the 3 minutes barriers down allowance for level crossings
Definition: TrainUnit.h:862
TRailGraphics::Code5
Graphics::TBitmap * Code5
Definition: GraphicUnit.h:995
TTrackElement::CallingOnSet
bool CallingOnSet
Used for for signals only when a train is being called on - used to plot the position lights.
Definition: TrackUnit.h:135
TUtilities::SaveFileInt
void SaveFileInt(std::ofstream &OutFile, int SaveInt)
stores the int value to the file, then a CR
Definition: Utilities.cpp:121
TTrain::IsLongServRefDisplayRequired
void IsLongServRefDisplayRequired(int Caller, TDisplay *Disp)
function that checks if long serv ref display needed and if so removes earlier display if plotted the...
Definition: TrainUnit.cpp:2462
TrainController
TTrainController * TrainController
the object pointer, one object only - created in InterfaceUnit
Definition: TrainUnit.cpp:56
TUtilities::FailureMode
TFailureMode FailureMode
specifies whether no failures or minor, moderate or major random failures are to be applied (added at...
Definition: Utilities.h:119
TTrain::BufferAtExit
bool BufferAtExit(int Caller, int Element, int Exitpos) const
True if Element is a buffer and Exitpos is the buffer end.
Definition: TrainUnit.cpp:3556
TTrain::IsTrainTerminating
bool IsTrainTerminating(int Caller)
True if train service terminates at its current location.
Definition: TrainUnit.cpp:7249
TTrainController::SecondPassMessage
void SecondPassMessage(bool GiveMessages, AnsiString Message)
Give a user message during timetable integrity checking if GiveMessages is true, ignore if false.
Definition: TrainUnit.cpp:16912
SignallerStepForward
@ SignallerStepForward
Definition: TrainUnit.h:57
TTrack::GetHLocMin
int GetHLocMin()
Definition: TrackUnit.h:901
TDisplay::GetOutputLog5
TLabel * GetOutputLog5()
Definition: DisplayUnit.h:165
TTrainController::LateExits
int LateExits
Definition: TrainUnit.h:883
TTrainController::OpTimeToActUpdateCounter
unsigned int OpTimeToActUpdateCounter
<List of all ServiceRefs that have two or more same locations without a cdt between - loaded during S...
Definition: TrainUnit.h:907
TTrain::BeingCalledOn
bool BeingCalledOn
in course of being called on to a station
Definition: TrainUnit.h:391
TUtilities::CheckFileInt
bool CheckFileInt(std::ifstream &InFile, int Lowest, int Highest)
checks that the value is an int lying between Lowest & Highest (inclusive), returns true for success
Definition: Utilities.cpp:238
TRailGraphics::ChangeForegroundColour2
void ChangeForegroundColour2(int Caller, Graphics::TBitmap *BitmapIn, Graphics::TBitmap *BitmapOut, TColor NewForegroundColour, TColor BackgroundColour)
New function to do the same as the above but with fewer pixel changes - for use in LoadSession to avo...
Definition: GraphicUnit.cpp:3674
TRailGraphics::Code_e
Graphics::TBitmap * Code_e
Definition: GraphicUnit.h:968
TTrain::CalcTimeToAct
float CalcTimeToAct(int Caller, float &TimeToExit, THVShortPair &ExitPair)
new v2.2.0 for operator action panel. Calculates the time left for operator action to avoid unnecessa...
Definition: TrainUnit.cpp:9619
TUtilities::SignalChangeEventsPerFailure
int SignalChangeEventsPerFailure
number of signal changes between failures - reciprocal of failure probability per change
Definition: Utilities.h:95
TTrain::HoldAtLocationInTTMode
bool HoldAtLocationInTTMode
true if actions are needed before train departs
Definition: TrainUnit.h:341
TTrack::TInfrastructureFailureEntry
Definition: TrackUnit.h:714
TTrain::PlotTrainInZoomOutMode
void PlotTrainInZoomOutMode(int Caller, bool Flash)
Plots the train on screen in zoomed-out mode, state of 'Flash' determines whether the flashing trains...
Definition: TrainUnit.cpp:9360
TTrain::LoadOneSessionTrain
void LoadOneSessionTrain(int Caller, std::ifstream &InFile)
Create one train with relevant member values from the sesion file.
Definition: TrainUnit.cpp:8639
TTrain::PlotBackgroundGraphic
void PlotBackgroundGraphic(int Caller, int ArrayNumber, TDisplay *Disp) const
Replot the graphic pointed to by BackgroundPtr (see above) after a train has passed.
Definition: TrainUnit.cpp:3548
TTrainController::TotLateArrMins
float TotLateArrMins
Definition: TrainUnit.h:868
TTrain::HOffset
int HOffset[4]
Definition: TrainUnit.h:509
FailMissedArrival
@ FailMissedArrival
Definition: TrainUnit.h:42
TTrainController::TrainDataVectorCopy
TTrainDataVector TrainDataVectorCopy
vector containing the internal timetable, the copy is used for conflict analysis only
Definition: TrainUnit.h:921
TTrain::UpdateCounter
unsigned int UpdateCounter
used in train splitting operations to prevent too frequent checks for a location being long enough fo...
Definition: TrainUnit.h:475
NewService
@ NewService
Definition: TrainUnit.h:55
FailEnterLockedRoute
@ FailEnterLockedRoute
Definition: TrainUnit.h:43
TTrainController::PlotAllTrainsInZoomOutMode
void PlotAllTrainsInZoomOutMode(int Caller, bool Flash)
Plots all trains on screen in zoomed-out mode, state of 'Flash' determines whether the flashing train...
Definition: TrainUnit.cpp:17713
TOneRoute
A descendent of TOnePrefDir used for routes. Used during contruction of a route (ConstructRoute) and ...
Definition: TrackUnit.h:1528
TTrain::SignallerStopped
bool SignallerStopped
Definition: TrainUnit.h:504
TTrain::BufferZoomOutFlashRequired
bool BufferZoomOutFlashRequired
set when train is at buffers and is to flash in zoomout mode (i.e. when reaches buffers unexpectedly ...
Definition: TrainUnit.h:393
TAllRoutes::TRouteType
TRouteType
Definition: TrackUnit.h:1680
TTrain::TrainDataEntryPtr
TTrainDataEntry * TrainDataEntryPtr
points to the current position in the timetable's TrainDataVector
Definition: TrainUnit.h:381
TTrain::LongServRefNameBitmap
Graphics::TBitmap * LongServRefNameBitmap
Definition: TrainUnit.h:528
TTrainDataEntry::StartSpeed
int StartSpeed
in km/h
Definition: TrainUnit.h:229
TTrainDataEntry::NumberOfTrains
int NumberOfTrains
number of repeats + 1
Definition: TrainUnit.h:225
TTrainController::Derailments
int Derailments
Definition: TrainUnit.h:875
clStationStopBackground
#define clStationStopBackground
Definition: GraphicUnit.h:302
TUtilities::CumulativeDelayedRandMinsAllTrains
int CumulativeDelayedRandMinsAllTrains
the running total of all random delays including knock-on delays for all trains, used to reduce total...
Definition: Utilities.h:101
TDisplay::GetOutputLog6
TLabel * GetOutputLog6()
Definition: DisplayUnit.h:170
ShuttleLink
@ ShuttleLink
Definition: TrainUnit.h:86
TRailGraphics::CodeM
Graphics::TBitmap * CodeM
Definition: GraphicUnit.h:1012
Crossover
@ Crossover
Definition: TrackUnit.h:66
TTrain::ClearToNextSignal
bool ClearToNextSignal(int Caller)
Checks forward from train LeadElement, following leading point attributes but ignoring trailing point...
Definition: TrainUnit.cpp:5240
AtLocation
@ AtLocation
Definition: TrainUnit.h:76
Signal
@ Signal
Definition: TrackUnit.h:76
TRailGraphics::Code_k
Graphics::TBitmap * Code_k
Definition: GraphicUnit.h:974
TRailGraphics::CodeF
Graphics::TBitmap * CodeF
Definition: GraphicUnit.h:1005
TTrack::ContinuationNameMap
std::map< AnsiString, char > ContinuationNameMap
map of all continuation names, char is a dummy
Definition: TrackUnit.h:803
TAllRoutes::GetRouteTypeAndNumber
TRouteType GetRouteTypeAndNumber(int Caller, int TrackVectorPosition, int LinkPos, int &RouteNumber)
Examines Route2MultiMap and if the element at TrackVectorPosition with LinkPos (can be entry or exit)...
Definition: TrackUnit.cpp:20159
TTrainController::TrainDataVector
TTrainDataVector TrainDataVector
Definition: TrainUnit.h:921
TTrainController::ConsolidateSARNTAtLoc
AnsiString ConsolidateSARNTAtLoc(int Caller, const AnsiString Input, int &NumTrainsAtLoc)
Removes duplicates from and sorts ServiceAndRepeatNumTotal into alphabetical order for AtLoc listing ...
Definition: TrainUnit.cpp:21096
TAllRoutes::NotAutoSigsRoute
@ NotAutoSigsRoute
Definition: TrackUnit.h:1681
TTrain::ExitTimeHalf
TDateTime ExitTimeHalf
Definition: TrainUnit.h:477
Exited
@ Exited
Definition: TrainUnit.h:92
TTrack::TrackElementAt
TTrackElement & TrackElementAt(int Caller, int At)
A range-checked version of TrackVector.at(At)
Definition: TrackUnit.cpp:11235
TTrain::ActionsSkippedFlag
bool ActionsSkippedFlag
prevents any further skipping until after the next departure
Definition: TrainUnit.h:339
TTrainController::LoadSessionTrains
void LoadSessionTrains(int Caller, std::ifstream &SessionFile)
load trains from a session file
Definition: TrainUnit.cpp:17411
TTrain::CheckOneSessionTrain
static bool CheckOneSessionTrain(std::ifstream &InFile)
Carries out an integrity check for the train section of a session file, if fails a message is given a...
Definition: TrainUnit.cpp:8926
TAllRoutes::GetRouteTypeAndGraphics
TRouteType GetRouteTypeAndGraphics(int Caller, int TrackVectorPosition, int LinkPos, Graphics::TBitmap *&EXGraphicPtr, Graphics::TBitmap *&EntryDirectionGraphicPtr)
Examines Route2MultiMap for the element at TrackVectorPosition with LinkPos (can be entry or exit).
Definition: TrackUnit.cpp:19985
TRailGraphics::CodeY
Graphics::TBitmap * CodeY
Definition: GraphicUnit.h:1024
TTrain::SetOneGraphicCode
Graphics::TBitmap * SetOneGraphicCode(char CodeChar)
Return a pointer to the graphic corresponding to the character 'CodeVhar'.
Definition: TrainUnit.cpp:2763
TTrain::DerailPending
bool DerailPending
Definition: TrainUnit.h:504
TTrainMode
TTrainMode
indicates train operating mode, 'None' for not in use
Definition: TrainUnit.h:63
TAllRoutes::TCallonEntry
Used to store relevant values when a call-on found, ready for plotting an unrestricted route.
Definition: TrackUnit.h:1702
TRailGraphics::Code_a
Graphics::TBitmap * Code_a
Definition: GraphicUnit.h:964
TTrain::StoppedAtLocation
bool StoppedAtLocation
Definition: TrainUnit.h:504
TTrainController::TContinuationTrainExpectationMultiMapIterator
TContinuationTrainExpectationMultiMap::iterator TContinuationTrainExpectationMultiMapIterator
iterator for the multimap
Definition: TrainUnit.h:795
TTrain::MaximumMassLimit
static const int MaximumMassLimit
kg (i.e. 10,000 tonnes)
Definition: TrainUnit.h:319
TTrainController::StopTTClockFlag
bool StopTTClockFlag
when true the timetable clock is stopped, used for messages display and train popup menu display etc
Definition: TrainUnit.h:839
FrontSplit
@ FrontSplit
Definition: TrainUnit.h:55
TTrain::MaxExitSpeed
double MaxExitSpeed
the maximum speed that the train can exit the next element
Definition: TrainUnit.h:441
TTrainController::BFHigh
bool BFHigh
Definition: TrainUnit.h:851
TRailGraphics::Code4
Graphics::TBitmap * Code4
Definition: GraphicUnit.h:994
TRailGraphics::Code_g
Graphics::TBitmap * Code_g
Definition: GraphicUnit.h:970
TTrain::LagEntryPos
int LagEntryPos
Definition: TrainUnit.h:377
SNTShuttle
@ SNTShuttle
Definition: TrainUnit.h:70
TTrain::NewDelay
double NewDelay
an additional random delay at a location (added at v2.13.0)
Definition: TrainUnit.h:453
TRailGraphics::CodeG
Graphics::TBitmap * CodeG
Definition: GraphicUnit.h:1006
Leave
@ Leave
Definition: TrainUnit.h:55
TTrainController::UnexpectedExits
int UnexpectedExits
Definition: TrainUnit.h:894
TTrainController::TLocServiceTimes::AtLocTime
AnsiString AtLocTime
Definition: TrainUnit.h:807
TTrainController::ServiceReference
AnsiString ServiceReference
String used to display the offending service in timetable error messages.
Definition: TrainUnit.h:835
TTrain::FirstLaterStopRecoverableTime
float FirstLaterStopRecoverableTime
this used to deduct from RecoverableTime when arrive at a location for OperatorActionpanel (OperatorA...
Definition: TrainUnit.h:467
TTrack::FailedSignalsVector
TFailedElementVector FailedSignalsVector
Definition: TrackUnit.h:801
TTrain::LeavingUnderSigControlAtContinuation
bool LeavingUnderSigControlAtContinuation
set when the train has reached an exit continuation when under signaller control, used to prevent the...
Definition: TrainUnit.h:407
TTrain::LongServRefTextH
int LongServRefTextH
stores the HPos position of the service ref train name
Definition: TrainUnit.h:490
TTrain::PlotTrainGraphic
void PlotTrainGraphic(int Caller, int ArrayNumber, TDisplay *Disp)
Plot the train's headcode character corresponding to ArrayNumber.
Definition: TrainUnit.cpp:3529
TRailGraphics::Code_j
Graphics::TBitmap * Code_j
Definition: GraphicUnit.h:973
FNil
@ FNil
Definition: Utilities.h:43
NoFormat
@ NoFormat
Definition: TrainUnit.h:70
TTrainController::TwoOrMoreLocationsWarningGiven
bool TwoOrMoreLocationsWarningGiven
new at v2.6.0 to allow loops
Definition: TrainUnit.h:847
FailBufferCrash
@ FailBufferCrash
Definition: TrainUnit.h:44
WaitingForFJO
@ WaitingForFJO
Definition: TrainUnit.h:44
FailMissedExitRailway
@ FailMissedExitRailway
Definition: TrainUnit.h:43
TTrainController::ControllerGetNewServiceDepartureInfo
AnsiString ControllerGetNewServiceDepartureInfo(int Caller, TActionVectorIterator Ptr, int RptNum, TTrainDataEntry *TDEPtr, TTrainDataEntry *LinkedTrainDataPtr, int IncrementalMinutes, int IncrementalDigits, AnsiString RetStr)
Similar to TTrain::GetNewServiceDepartureInfo for use in ContinuationEntryFloatingTTString.
Definition: TrainUnit.cpp:11087
TimeTimeLoc
@ TimeTimeLoc
Definition: TrainUnit.h:70
TTrain::GetOffsetValues
void GetOffsetValues(int Caller, int &HOffset, int &VOffset, int Link) const
Sets HOffset & VOffset (see above) for a single headcode character depending on the Link value.
Definition: TrainUnit.cpp:3074
TTrain::TimeToExit
float TimeToExit
in minutes: new for multiplayer, -1 = > 60 mins
Definition: TrainUnit.h:465
TTrain::Derailed
bool Derailed
Definition: TrainUnit.h:504
TRailGraphics::ChangeSpecificColour
void ChangeSpecificColour(int Caller, Graphics::TBitmap *BitmapIn, Graphics::TBitmap *BitmapOut, TColor ColourToBeChanged, TColor NewColour)
Definition: GraphicUnit.cpp:3703
Enter
@ Enter
Definition: TrainUnit.h:55
TRailGraphics::bm93set
Graphics::TBitmap * bm93set
Definition: GraphicUnit.h:518
TTrack::GetTrackVectorPositionFromString
int GetTrackVectorPositionFromString(int Caller, AnsiString String, bool GiveMessages)
Takes the ElementID value (an AnsiString) (e.g. "8-13", "N43-N127", etc) and returns the correspondin...
Definition: TrackUnit.cpp:8099
TRailGraphics::Code_q
Graphics::TBitmap * Code_q
Definition: GraphicUnit.h:980
TTrainController::SaveSessionLockedRoutes
void SaveSessionLockedRoutes(int Caller, std::ofstream &SessionFile)
save locked routes to a session file
Definition: TrainUnit.cpp:17460
TTrainController::LongServRefFont
TFont * LongServRefFont
the font used for long serv ref names
Definition: TrainUnit.h:925
TTrain::OriginalPowerAtRail
double OriginalPowerAtRail
new at v2.4.0 to store value before a failure so it can be restored from here when repaired
Definition: TrainUnit.h:459
TTrainFormattedInformation
Contains all information for a single timetable entry for use in the formatted timetable.
Definition: TrainUnit.h:282
TTrainController::IsSNTEntryLocated
bool IsSNTEntryLocated(int Caller, const TTrainDataEntry &TDEntry, AnsiString &LocationName)
New trains introduced with 'Snt' may be at a timetabled location or elsewhere. This function checks a...
Definition: TrainUnit.cpp:16092
TActionVectorEntry::NewDescription
AnsiString NewDescription
Definition: TrainUnit.h:125
TTrainController::TContinuationTrainExpectationEntry::VectorPosition
int VectorPosition
TrackVectorPosition for the continuation element.
Definition: TrainUnit.h:787
TTrain::NewTrainService
void NewTrainService(int Caller, bool NoLogFlag)
Carry out the actions needed when a train forms a new service (code Fns)
Definition: TrainUnit.cpp:6682
TTrainController::TTrainController
TTrainController()
Constructor.
Definition: TrainUnit.cpp:10057
Timetable
@ Timetable
Definition: TrainUnit.h:64
TUtilities::SaveFileDouble
void SaveFileDouble(std::ofstream &OutFile, double SaveDouble)
converts the double value to a string (if double stored directly it is truncated to 6 digits) then st...
Definition: Utilities.cpp:127
TTrainController::GetRepeatHeadCode
AnsiString GetRepeatHeadCode(int Caller, AnsiString BaseHeadCode, int RepeatNumber, int IncDigits)
Return the service headcode for the repeat service.
Definition: TrainUnit.cpp:16477
StartSequence
@ StartSequence
Definition: TrainUnit.h:81
TTrain::AbleToMoveButForSignal
bool AbleToMoveButForSignal(int Caller)
Indicates that a train is only prevented from moving by a signal - used to allow appropriate popup me...
Definition: TrainUnit.cpp:7336
TTrainController::OpActionPanelVisible
bool OpActionPanelVisible
new v2.2.0 flag to prevent time to act functions when not visible
Definition: TrainUnit.h:845
TTrack::GetAnyElementOppositeLinkPos
int GetAnyElementOppositeLinkPos(int Caller, int TrackVectorPosition, int LinkPos, bool &Derail)
Return the opposite link position for the element at TrackVectorPosition with link position LinkPos,...
Definition: TrackUnit.cpp:12109
TTrack::GapFlashRedPosition
int GapFlashRedPosition
TrackVectorPosition of the gap element that is flashing green or red.
Definition: TrackUnit.h:793
TActionVectorEntry::NumberOfRepeats
int NumberOfRepeats
the number of repeating services
Definition: TrainUnit.h:133
TAllRoutes::GetFixedRouteAt
const TOneRoute & GetFixedRouteAt(int Caller, int At) const
Returns a constant reference to the route at AllRoutesVector position 'At', after performing range ch...
Definition: TrackUnit.cpp:19831
TUtilities::clTransparent
TColor clTransparent
the display background colour, can be white, black or dark blue
Definition: Utilities.h:115
TTrainController::SignalStopWarning
bool SignalStopWarning
Definition: TrainUnit.h:837
TPrefDirElement::GetELinkPos
int GetELinkPos() const
Returns the ELink array position.
Definition: TrackUnit.h:275
TTrainController::CalcOperatingAndNotStartedTrainLateness
void CalcOperatingAndNotStartedTrainLateness(int Caller)
calculates additional lateness values for trains that haven't reached their destinations yet
Definition: TrainUnit.cpp:22065
ShuttleLinkTypeForRepeatEntry
@ ShuttleLinkTypeForRepeatEntry
Definition: TrainUnit.h:86
TTrain::SignallerStopBrakeRate
double SignallerStopBrakeRate
the train brake rate when stopping under signaller control
Definition: TrainUnit.h:449
TTrainController::TLocServiceTimes::Location
AnsiString Location
Definition: TrainUnit.h:805
TOneCompleteFormattedTrain
A single train with its headcode + list of actions for use in the formatted timetable.
Definition: TrainUnit.h:269
TTrainController::SignallerTrainRemovedOnAutoSigsRoute
bool SignallerTrainRemovedOnAutoSigsRoute
true if train was on an AutoSigsRoute when removed by the signaller
Definition: TrainUnit.h:843
TTrack::InactiveTrackVector
TTrackVector InactiveTrackVector
Definition: TrackUnit.h:835
TTrain::LogAction
void LogAction(int Caller, AnsiString HeadCode, AnsiString OtherHeadCode, TActionType ActionType, AnsiString LocationName, AnsiString SplitDistribution, TDateTime TimetableNonRepeatTime, bool Warning)
Send a message to the performance log and performance file, and if the message is flagged as a warnin...
Definition: TrainUnit.cpp:5747
Terminate
@ Terminate
Definition: TrainUnit.h:55
TTrack::ActiveTrackElementNameMap
TActiveTrackElementNameMap ActiveTrackElementNameMap
< map of coupled continuations
Definition: TrackUnit.h:807
TTrain::Crashed
bool Crashed
Definition: TrainUnit.h:504
TTrackElement::HLoc
int HLoc
Definition: TrackUnit.h:149
TTrain::HeadCodePosition
Graphics::TBitmap * HeadCodePosition[4]
Set from the HeadCodeGrPtr[4] pointer values, HeadCodePosition[0] is always the front,...
Definition: TrainUnit.h:520
TTrain::TrainID
int TrainID
the train's identification number
Definition: TrainUnit.h:379
Running
@ Running
Definition: TrainUnit.h:92
TAllFormattedTrains
std::vector< TTrainFormattedInformation > TAllFormattedTrains
vector of all timetabled trains for use in the formatted timetable
Definition: TrainUnit.h:291
TRailGraphics::ChangeBackgroundColour3
void ChangeBackgroundColour3(int Caller, Graphics::TBitmap *BitmapIn, Graphics::TBitmap *BitmapOut, TColor NewBackgroundColour, TColor OldBackgroundColour)
as above but uses Scanline
Definition: GraphicUnit.cpp:3800
TRailGraphics::smCyan
Graphics::TBitmap * smCyan
Definition: GraphicUnit.h:899
TTrain::LongServRefTextV
int LongServRefTextV
stores the VPos position of the service ref train name
Definition: TrainUnit.h:492
TTrainController::MovingSuccessor
bool MovingSuccessor(const TActionVectorEntry &AVEntry)
A shorthand function that returns true if the successor to a given timetable action command should be...
Definition: TrainUnit.cpp:15389
TTrainController::LogActionError
void LogActionError(int Caller, AnsiString HeadCode, AnsiString OtherHeadCode, TActionEventType ActionEventType, AnsiString LocationID)
Send an error message to the performance log and file, and as a warning if appropriate.
Definition: TrainUnit.cpp:16950
TTrainController::TrainVectorAt
TTrain & TrainVectorAt(int Caller, int VecPos)
Return a reference to the train at position VecPos in the TrainVector, carries out range checking on ...
Definition: TrainUnit.cpp:17736
TTrain::FrontElementSpeedLimit
int FrontElementSpeedLimit
Definition: TrainUnit.h:469
TTrack::NumberOfPlatforms
int NumberOfPlatforms(int Caller, AnsiString LocationName)
Returns the number of separate platforms (not platform elements) at a given location,...
Definition: TrackUnit.cpp:12329
TTrack::TSigElement::SpeedTag
int SpeedTag
the TrackElement SpeedTag value - specifies the signal element
Definition: TrackUnit.h:728
TTrain::DeleteTrain
void DeleteTrain(int Caller)
This is a housekeeping function to delete train heap objects (bitmaps) explicitly rather than by usin...
Definition: TrainUnit.cpp:262
TTrainController::NumFailures
int NumFailures
Definition: TrainUnit.h:895
TTrain::ExitSpeedFull
double ExitSpeedFull
speed when leaving the next element
Definition: TrainUnit.h:435
TTrain::SetHeadCodeGraphics
void SetHeadCodeGraphics(int Caller, AnsiString Code)
Set the four HeadCodeGrPtr[4] pointers to the appropriate character graphics with the current backgro...
Definition: TrainUnit.cpp:2960
TAllRoutes::SetTrailingSignalsOnAutoSigsRoute
void SetTrailingSignalsOnAutoSigsRoute(int Caller, int TrackVectorPosition, int XLinkPos)
Enter with signal at TrackVectorElement already set to red by the passing train.
Definition: TrackUnit.cpp:20841
clB5G5R5
#define clB5G5R5
Definition: GraphicUnit.h:287
TUtilities::TimeStamp
AnsiString TimeStamp()
creates a string of the form 'hh:mm:ss' for use in call & event logging
Definition: Utilities.cpp:73
TTrainController::RandomFailureCounter
unsigned int RandomFailureCounter
new at v2.4.0 for train failures, resets after 53 seconds (53 prime so can trigger at any clock time)
Definition: TrainUnit.h:911
TAllRoutes::TLockedRouteClass::RouteNumber
int RouteNumber
the vector position number of the relevant route in AllRoutesVector
Definition: TrackUnit.h:1667
TTrainController::AddTrain
bool AddTrain(int Caller, int RearPosition, int FrontPosition, AnsiString HeadCode, int StartSpeed, int Mass, double MaxRunningSpeed, double MaxBrakeRate, double PowerAtRail, AnsiString ModeStr, TTrainDataEntry *TrainDataEntryPtr, int RepeatNumber, int IncrementalMinutes, int IncrementalDigits, int SignallerSpeed, bool SignallerControl, TActionEventType &EventType)
Introduce a new train to the railway, with the characteristics specified, returns true for success,...
Definition: TrainUnit.cpp:10570
TTrainController::~TTrainController
~TTrainController()
Destructor.
Definition: TrainUnit.cpp:10139
TTrain::RepeatShuttleOrNewNonRepeatService
void RepeatShuttleOrNewNonRepeatService(int Caller, bool NoLogFlag)
Carry out the actions needed to create either a new shuttle service or (if all repeats have finished)...
Definition: TrainUnit.cpp:7161
TTrainController::TwoLocationList
TServiceCallingLocsList TwoLocationList
Definition: TrainUnit.h:905
TTrain::GetNewServiceDepartureInfo
AnsiString GetNewServiceDepartureInfo(int Caller, TActionVectorEntry *Ptr, int RptNum, TTrainDataEntry *LinkedTrainDataPtr, AnsiString RetStr, bool TimetableTime)
called during FloatingLabelNextString to find the next service departure time & next location (last b...
Definition: TrainUnit.cpp:7972
TTrain::Description
AnsiString Description
needs own HeadCode & description because repeat entries will differ from TrainDataEntry values (Descr...
Definition: TrainUnit.h:333
TUtilities::CheckAndReadFileInt
bool CheckAndReadFileInt(std::ifstream &InFile, int Lowest, int Highest, int &OutInt)
checks that the value is an int lying between Lowest & Highest (inclusive), returns true for success ...
Definition: Utilities.cpp:280
TRailGraphics::Code7
Graphics::TBitmap * Code7
Definition: GraphicUnit.h:997
TTrackElement::ActiveTrackElementName
AnsiString ActiveTrackElementName
Location name used either in the timetable or for a continuation (continuation names not used in time...
Definition: TrackUnit.h:128
TRailGraphics::bm94set
Graphics::TBitmap * bm94set
Definition: GraphicUnit.h:520
TUtilities::DelayMode
TDelayMode DelayMode
specifies whether no delays or minor, moderate or major random delays are to be applied (added at v2....
Definition: Utilities.h:117
TTrainController::LogEvent
void LogEvent(AnsiString Str)
store Str to the event log - moved from TUtilities for v0.6 so can record the tt clock value
Definition: TrainUnit.cpp:10151
TRailGraphics::CodeO
Graphics::TBitmap * CodeO
Definition: GraphicUnit.h:1014
TTrain::ExitPair
THVShortPair ExitPair
H & V coordinates of the exit element related to TimeToExit, new for multiplayer.
Definition: TrainUnit.h:483
TAllRoutes::SignallerRemovedTrainAutoRoute
TOneRoute SignallerRemovedTrainAutoRoute
if train was on an AutoSigsRoute when removed then this stores the route so that signals can be reset
Definition: TrackUnit.h:1754
TTrainController::MassHigh
bool MassHigh
Definition: TrainUnit.h:851
TTrainController::CheckAndPopulateListOfIDs
bool CheckAndPopulateListOfIDs(int Caller, AnsiString IDSet, TNumList &ExitList, bool GiveMessages)
Used to compile ExitList from a string list of element IDs, returns true for success or gives a messa...
Definition: TrainUnit.cpp:12817
TTrain::PlotTrainWithNewBackgroundColour
void PlotTrainWithNewBackgroundColour(int Caller, TColor NewBackgroundColour, TDisplay *Disp)
Changes the train's background colour (e.g. to pale green if stopped at a station) Note that this use...
Definition: TrainUnit.cpp:4028
TUtilities::SaveFileString
void SaveFileString(std::ofstream &OutFile, AnsiString SaveString)
stores the string value to the file, then a '0' delimiter then a CR
Definition: Utilities.cpp:135
TTrain::PlotAlternativeTrackRouteGraphic
void PlotAlternativeTrackRouteGraphic(int Caller, unsigned int LagElement, int LagELinkPos, int HOffset, int VOffset, TStraddle StraddleValue)
When a train moves off a bridge the other track may contain a route or have a train on it that has be...
Definition: TrainUnit.cpp:3728
TTrain::ExitTimeFull
TDateTime ExitTimeFull
times used in SetTrainMovementValues corresponding to the next element the train runs on
Definition: TrainUnit.h:477
TTrainController::CalcDistanceToRedSignalandStopTime
int CalcDistanceToRedSignalandStopTime(int Caller, int TrackVectorPosition, int TrackVectorPositionEntryPos, bool SigControlAndCanPassRedSignal, TActionVectorEntry *AVPtr, AnsiString HeadCode, int TrainID, float &CurrentStopTime, float &LaterStopTime, float &RecoverableTime, int &AvTrackSpeed, int &DistanceToExit, THVShortPair &ExitPair)
new v2.2.0 (DistanceToExit added for multiplayer), calcs distances to red signal & exit,...
Definition: TrainUnit.cpp:22378
TTrack::GapFlashRed
TGraphicElement * GapFlashRed
the red & green circle graphics used to show where the gaps are
Definition: TrackUnit.h:815
LocTypeForRepeatEntry
@ LocTypeForRepeatEntry
Definition: TrainUnit.h:76
SignallerChangeDirection
@ SignallerChangeDirection
Definition: TrainUnit.h:57
TAllRoutes::TLockedRouteClass
Handles routes that are locked because of approaching trains.
Definition: TrackUnit.h:1665
TTrain::TrainAtLocation
bool TrainAtLocation(int Caller, AnsiString &LocationName)
True when the train is stopped at a timetabled location.
Definition: TrainUnit.cpp:9507
TTrain::IsTrainIDOnBridgeTrackPos01
bool IsTrainIDOnBridgeTrackPos01(int Caller, unsigned int TrackVectorPosition)
True if train is on a bridge on trackpos 0 & 1.
Definition: TrainUnit.cpp:3592
TActionVectorEntry
Contains a single train action in a timetable - repeat entry is also of this class though no train ac...
Definition: TrainUnit.h:123
TTrainController::TotLateExitMins
float TotLateExitMins
Definition: TrainUnit.h:871
TTrainController::CheckSessionContinuationAutoSigEntries
bool CheckSessionContinuationAutoSigEntries(int Caller, std::ifstream &SessionFile)
Part of the session file integrity check for ContinuationAutoSigEntries, true for success.
Definition: TrainUnit.cpp:17582
TOneCompleteFormattedTrain::OneFormattedTrainVector
TOneFormattedTrainVector OneFormattedTrainVector
Definition: TrainUnit.h:272
TRailGraphics::Code1
Graphics::TBitmap * Code1
Definition: GraphicUnit.h:991
TTrainController::TimetableMessage
void TimetableMessage(bool GiveMessages, AnsiString Message)
Sends a message to the user if GiveMessages is true, including ServiceReference (see above) if not nu...
Definition: TrainUnit.cpp:16891
TDisplay
Definition: DisplayUnit.h:50
TTrackElement::ConnLinkPos
int ConnLinkPos[4]
Connecting element link position (i.e. array positions of the connecting element links,...
Definition: TrackUnit.h:147
TTrainController::ContinuationAutoSigVector
TContinuationAutoSigVector ContinuationAutoSigVector
vector for TContinuationAutoSigEntry objects
Definition: TrainUnit.h:913
TTrain::StoppedWithoutPower
bool StoppedWithoutPower
Definition: TrainUnit.h:505
TTrain::NameInTimetableBeforeCDT
int NameInTimetableBeforeCDT(int Caller, AnsiString Name, bool &Stop)
Returns the number by which the train ActionVectorEntryPtr needs to be incremented to point to the lo...
Definition: TrainUnit.cpp:5177
TimeCmdDescription
@ TimeCmdDescription
Definition: TrainUnit.h:71
TTrainFormattedInformation::Header
AnsiString Header
description, mass, power, brake rate etc
Definition: TrainUnit.h:284
TActionEventType
TActionEventType
Used for reporting error conditions & warnings.
Definition: TrainUnit.h:40
TTrainController::TrainExistsAtIdent
bool TrainExistsAtIdent(int Caller, int TrainID)
new at v2.4.0 return true if find the train (added at v2.4.0 as can select a removed train in Actions...
Definition: TrainUnit.cpp:10904
SignallerJoin
@ SignallerJoin
Definition: TrainUnit.h:56
TTrain::RearStartElement
int RearStartElement
start TrackVectorPosition element for rear of train
Definition: TrainUnit.h:367
TTrain::StepForwardFlag
bool StepForwardFlag
set when the signaller command to step forward one element has been given
Definition: TrainUnit.h:417
TTrainController::SplitTrainInfo
bool SplitTrainInfo(int Caller, AnsiString TrainInfoStr, AnsiString &HeadCode, AnsiString &Description, int &StartSpeed, int &MaxRunningSpeed, int &Mass, double &MaxBrakeRate, double &PowerAtRail, int &SignallerSpeed, bool GiveMessages)
Parse a train information entry, return true for success; PowerAtRail changed to double& from int& at...
Definition: TrainUnit.cpp:12917
TTrainController::TotEarlyExitMins
float TotEarlyExitMins
Definition: TrainUnit.h:867
TFixedTrackPiece::Config
TConfiguration Config[4]
the type of link - see TConfiguration above
Definition: TrackUnit.h:97
TTrainController::Operate
void Operate(int Caller)
called every clock tick to introduce new trains and update existing trains
Definition: TrainUnit.cpp:10165
TTrackElement::Failed
bool Failed
New parameter added at v2.13.0 for failed points, signals & TSRs.
Definition: TrackUnit.h:141
TTrackElement::TrainIDOnElement
int TrainIDOnElement
Definition: TrackUnit.h:155
TTrainController::SkippedTTEvents
int SkippedTTEvents
Definition: TrainUnit.h:890
TRailGraphics::smRed
Graphics::TBitmap * smRed
Definition: GraphicUnit.h:906
TTrain::PowerAtRail
double PowerAtRail
in Watts (taken as 80% of the train's Gross Power, i.e. that entered by the user)
Definition: TrainUnit.h:457
TRailGraphics::LCPlainMan
Graphics::TBitmap * LCPlainMan
Definition: GraphicUnit.h:750
TDisplay::GetOutputLog4
TLabel * GetOutputLog4()
Definition: DisplayUnit.h:160
TAllRoutes::RebuildRailwayFlag
bool RebuildRailwayFlag
this is set whenever a route has to be cancelled forcibly in order to force a ClearandRebuildRailway ...
Definition: TrackUnit.h:1735
TTrainDataEntry::MaxRunningSpeed
double MaxRunningSpeed
in km/h
Definition: TrainUnit.h:219
TUtilities::LoadFileInt
int LoadFileInt(std::ifstream &InFile)
loads an int value from the file
Definition: Utilities.cpp:162
TTrackElement::ElementID
AnsiString ElementID
the element identifier based on position in the railway
Definition: TrackUnit.h:130
TUtilities::MinorDelayCutoff
float MinorDelayCutoff
Definition: Utilities.h:50
TTimeToExitMultiMapEntry
std::pair< THVShortPair, TExitInfo > TTimeToExitMultiMapEntry
Definition: TrainUnit.h:117
TRailGraphics::Code6
Graphics::TBitmap * Code6
Definition: GraphicUnit.h:996
TTrainDataEntry::TrainOperatingDataVector
TTrainOperatingDataVector TrainOperatingDataVector
operating information for the train including all its repeats
Definition: TrainUnit.h:233
TTrain::DelayedRandMins
double DelayedRandMins
the remaining random delay at any point in time for the train (added at v2.13.0)
Definition: TrainUnit.h:451
TTrackElement::VLoc
int VLoc
The h & v locations in the railway (top lh corner of the first build screen = 0,0)
Definition: TrackUnit.h:149
TTrainController::OperatingTrainLateMins
float OperatingTrainLateMins
total late minutes of operating trains on exit operation for locations not reached yet
Definition: TrainUnit.h:860
TTrack::GetNonPointsOppositeLinkPos
int GetNonPointsOppositeLinkPos(int LinkPosIn)
Return the corresponding link position (track always occupies either links 0 & 1 or 2 & 3)
Definition: TrackUnit.h:917
TTrain::UnplotTrain
void UnplotTrain(int Caller)
Unplot train from screen in zoomed-in mode.
Definition: TrainUnit.cpp:589
TTrain::IsThereAnAdjacentTrain
bool IsThereAnAdjacentTrain(int Caller, TTrain *&TrainToBeJoinedBy)
Definition: TrainUnit.cpp:5663
TTrain::TRSTime
TDateTime TRSTime
Definition: TrainUnit.h:479
clB1G0R0
#define clB1G0R0
Definition: GraphicUnit.h:79
TAllRoutes::TLockedRouteClass::LastTrackVectorPosition
unsigned int LastTrackVectorPosition
the TrackVector position of the last (i.e. most forward) element in the route (this will be truncated...
Definition: TrackUnit.h:1671
FailMissedJBO
@ FailMissedJBO
Definition: TrainUnit.h:42
TUtilities::EventLog
std::deque< AnsiString > EventLog
event store, saved to the errorlog for diagnostic purposes
Definition: Utilities.h:107
TAllRoutes::NoRoute
@ NoRoute
Definition: TrackUnit.h:1681
TActionVectorEntry::FormatType
TTimetableFormatType FormatType
defines the timetable action type
Definition: TrainUnit.h:141
TTrain::RemainHereLogNotSent
bool RemainHereLogNotSent
flag to prevent repeated logs, new at v1.2.0
Definition: TrainUnit.h:345
TRailGraphics::CodeU
Graphics::TBitmap * CodeU
Definition: GraphicUnit.h:1020
TTrainController::RestartTime
TDateTime RestartTime
TTClockTime when operation pauses ( = timetable start time prior to operation) TTClockTime is calcula...
Definition: TrainUnit.h:750
Moderate
@ Moderate
Definition: Utilities.h:38
TTrainController::TimetableStartTime
TDateTime TimetableStartTime
the start time of the current timetable
Definition: TrainUnit.h:748
clSPADBackground
#define clSPADBackground
Definition: GraphicUnit.h:301
TTrainController::UnplotTrains
void UnplotTrains(int Caller)
unplot all trains from screen
Definition: TrainUnit.cpp:10554
TRailGraphics::smBrightGreen
Graphics::TBitmap * smBrightGreen
Definition: GraphicUnit.h:897
TTrainController::EarlyArrivals
int EarlyArrivals
Definition: TrainUnit.h:876
FailCreateTrain
@ FailCreateTrain
Definition: TrainUnit.h:41
TTrain::IncrementalDigits
int IncrementalDigits
the number of digits to increment by in repeat entries
Definition: TrainUnit.h:363
TTrainController::CreateFormattedTimetable
void CreateFormattedTimetable(int Caller, AnsiString RailwayTitle, AnsiString TimetableTitle, AnsiString CurDir)
Examines the internal timetable (TrainDataVector) and creates from it a chronological (....
Definition: TrainUnit.cpp:17749
TTrack::RouteFlashFlag
bool RouteFlashFlag
true while a route is flashing prior to being set
Definition: TrackUnit.h:777
TUtilities::ShowLongServRefsFlag
bool ShowLongServRefsFlag
when set long service references show on screen, initialised in Interface constructor
Definition: Utilities.h:81
TimeCmdMaxSpeed
@ TimeCmdMaxSpeed
Definition: TrainUnit.h:71
TRailGraphics::Code_v
Graphics::TBitmap * Code_v
Definition: GraphicUnit.h:985
TTrackElement::StationEntryStopLinkPos1
int StationEntryStopLinkPos1
Definition: TrackUnit.h:153
Major
@ Major
Definition: Utilities.h:38
Points
@ Points
Definition: TrackUnit.h:66
TTrainController::SplitEntry
bool SplitEntry(int Caller, AnsiString OneEntry, bool GiveMessages, bool CheckLocationsExistInRailway, AnsiString &First, AnsiString &Second, AnsiString &Third, AnsiString &Fourth, int &RearStartOrRepeatMins, int &FrontStartPosition, TTimetableFormatType &TimetableFormatType, TTimetableLocationType &LocationType, TTimetableSequenceType &SequenceType, TTimetableShuttleLinkType &ShuttleLinkType, TNumList &ExitList, bool &Warning)
Parse a single timetable service action, return true for success.
Definition: TrainUnit.cpp:12284
TRailGraphics::LCPlain
Graphics::TBitmap * LCPlain
Definition: GraphicUnit.h:743
TTrain::LastActionDelayFlag
bool LastActionDelayFlag
used when trains join to ensure that there is a 30 second delay before the actual join takes place af...
Definition: TrainUnit.h:403
TTrainController::TotLatePassMins
float TotLatePassMins
Definition: TrainUnit.h:870
TTrainController::StripSpaces
void StripSpaces(int Caller, AnsiString &Input)
Strip both leading and trailing spaces at ends of Input and spaces before and after all commas and se...
Definition: TrainUnit.cpp:15991
TTrain::RemoveLongServRef
void RemoveLongServRef(int Caller, AnsiString NameText, TDisplay *Disp)
Removes the displayed train service ref.
Definition: TrainUnit.cpp:2712
FailMissedJoinOther
@ FailMissedJoinOther
Definition: TrainUnit.h:42
TTrain::JoinedOtherTrainFlag
bool JoinedOtherTrainFlag
true when the train has joined another train following an 'Fjo' timetable command or a signaller join...
Definition: TrainUnit.h:401
TTrainController::OpActionPanelHintDelayCounter
unsigned int OpActionPanelHintDelayCounter
new v2.2.0 on start operation delays the op action panel headcode display for about 3 secs while hint...
Definition: TrainUnit.h:909
TTrainController::BFLow
bool BFLow
Definition: TrainUnit.h:851
TTrainController::TotArrDepPass
int TotArrDepPass
Definition: TrainUnit.h:893
SignallerPassRedSignal
@ SignallerPassRedSignal
Definition: TrainUnit.h:57
TRailGraphics::ChangeForegroundColour
void ChangeForegroundColour(int Caller, Graphics::TBitmap *BitmapIn, Graphics::TBitmap *BitmapOut, TColor NewForegroundColour, TColor BackgroundColour)
Definition: GraphicUnit.cpp:3648
TRailGraphics::CodeE
Graphics::TBitmap * CodeE
Definition: GraphicUnit.h:1004
FailLockedRoute
@ FailLockedRoute
Definition: TrainUnit.h:41
TTrain::SignallerRemoved
bool SignallerRemoved
set when removed under signaller control to force a removal from the display at the next clock tick
Definition: TrainUnit.h:411
clB5G3R0
#define clB5G3R0
Definition: GraphicUnit.h:268
TTrain::FrontCodePtr
Graphics::TBitmap * FrontCodePtr
points to the front headcode segment, this is set to red or blue depending on TrainMode
Definition: TrainUnit.h:524
TOneRoute::SetRouteSignals
void SetRouteSignals(int Caller) const
Called when setting a route to set all signals appropriately. Also called when a new train is added a...
Definition: TrackUnit.cpp:18333
Continuation
@ Continuation
Definition: TrackUnit.h:66
TTrainController::CallOnWarning
bool CallOnWarning
Definition: TrainUnit.h:837
TTrain::ActualArrivalTime
TDateTime ActualArrivalTime
location departure time and 'train ready to start' time (TRSTime is 10 seconds before the ReleaseTime...
Definition: TrainUnit.h:479
TTrack::GetFilletGraphic
Graphics::TBitmap * GetFilletGraphic(int Caller, TTrackElement TrackElement)
Return a pointer to the point fillet (the bit that appears to move when points are changed) for the p...
Definition: TrackUnit.cpp:7857
GraphicUnit.h
PerfLogUnit.h
TextUnit.h
TUtilities::MajorDelayFactor
float MajorDelayFactor
Definition: Utilities.h:55
TTrack::PointFlashFlag
bool PointFlashFlag
< true if a route set through an LC that is closed to trains (& therefore needs to be opened)
Definition: TrackUnit.h:773
TTrack::ThisLocationLongEnoughForSplit
bool ThisLocationLongEnoughForSplit(int Caller, AnsiString HeadCode, int TrainID, AnsiString LocationName, int LeadElement, int LeadExitPos, int MidElement, int MidEntryPos, int &FrontTrainFrontPos, int &FrontTrainRearPos, int &RearTrainFrontPos, int &RearTrainRearPos, bool &TemporaryDelay)
checks if the track that the train is on is long enough for a split, returns false if not,...
Definition: TrackUnit.cpp:11555
TTrainController::OnTimeDeps
int OnTimeDeps
Definition: TrainUnit.h:886
TTrain::RestoreTimetableLocation
AnsiString RestoreTimetableLocation
stores the location name at which signaller control is taken, to ensure that it is back at that locat...
Definition: TrainUnit.h:496
TTrainController::TimetableIntegrityCheck
bool TimetableIntegrityCheck(int Caller, char *FileName, bool GiveMessages, bool CheckLocationsExistInRailway)
Checks overall timetable integrity, calls many other specific checking functions, returns true for su...
Definition: TrainUnit.cpp:11330
AllRoutes
TAllRoutes * AllRoutes
the object pointer, object created in InterfaceUnit
Definition: TrackUnit.cpp:54
TTrain::SignallerMaxSpeed
int SignallerMaxSpeed
maximum train speed under signaller control (in km/h)
Definition: TrainUnit.h:373
NoEvent
@ NoEvent
Definition: TrainUnit.h:41
FailSplitDueToOtherTrain
@ FailSplitDueToOtherTrain
Definition: TrainUnit.h:41
TTrainController::SendPerformanceSummary
void SendPerformanceSummary(int Caller, std::ofstream &PerfFile)
At the end of operation a summary of overall performance is sent to the performance file by this func...
Definition: TrainUnit.cpp:21569
TTrack::TInfrastructureFailureEntry::FailureTime
TDateTime FailureTime
Definition: TrackUnit.h:716
TTrain::ResetTrainElementID
void ResetTrainElementID(int Caller, unsigned int TrackVectorPosition, int EntryPos)
After a train has moved off an element that element has its TrainIDOnElement value set back to -1 to ...
Definition: TrainUnit.cpp:3686
TRailGraphics::smMagenta
Graphics::TBitmap * smMagenta
Definition: GraphicUnit.h:902
RepairFailedTrain
@ RepairFailedTrain
Definition: TrainUnit.h:57
TTrainController::AllServiceCallingLocsMap
TAllServiceCallingLocsMap AllServiceCallingLocsMap
Definition: TrainUnit.h:818
TRailGraphics::smPaleGreen
Graphics::TBitmap * smPaleGreen
Definition: GraphicUnit.h:905
TRailGraphics::CodeS
Graphics::TBitmap * CodeS
Definition: GraphicUnit.h:1018
TTrainController::ContinuationEntryFloatingTTString
AnsiString ContinuationEntryFloatingTTString(int Caller, TTrainDataEntry *TTDEPtr, int RepeatNumber, int IncrementalMinutes, int IncrementalDigits)
Build string for use in floating window for expected trains at continuations.
Definition: TrainUnit.cpp:10935
TFixedTrackPiece::SpeedTag
int SpeedTag
The element identification number - corresponds to the relevant SpeedButton->Tag.
Definition: TrackUnit.h:88
TTrain::FollowOnServiceRef
AnsiString FollowOnServiceRef
Definition: TrainUnit.h:335
TTrain::SPADFlag
bool SPADFlag
set when running past a red signal without permission flags to indicate relevant stop conditions or p...
Definition: TrainUnit.h:502
TTrain::Stopped
bool Stopped()
True if the train has stopped for any reason.
Definition: TrainUnit.h:708
Minor
@ Minor
Definition: Utilities.h:38
TTrack::SigTableGroundSignal
TSigElement SigTableGroundSignal[40]
new at version 0.6 for ground signals
Definition: TrackUnit.h:742
TTrain::TrainToJoinIsAdjacent
bool TrainToJoinIsAdjacent(int Caller, TTrain *&TrainToJoin)
True for a train waiting to join another when the other train is adjacent.
Definition: TrainUnit.cpp:6976
TActionVectorEntry::ExitList
TNumList ExitList
the list of valid train exit TrackVector positions for 'Fer' entries (empty to begin with)
Definition: TrainUnit.h:139
TTrainController::OnTimeExits
int OnTimeExits
Definition: TrainUnit.h:888
TTrain::OldZoomOutElement
int OldZoomOutElement[3]
stores the Lead, Mid & Lag TrackVectorPositions, used for unplotting trains from the old position in ...
Definition: TrainUnit.h:511
TPrefDirElement::GetELink
int GetELink() const
Returns ELink.
Definition: TrackUnit.h:269
TDisplay::GetOutputLog8
TLabel * GetOutputLog8()
Definition: DisplayUnit.h:180
WaitingForJBO
@ WaitingForJBO
Definition: TrainUnit.h:44
TUtilities::CheckFileDouble
bool CheckFileDouble(std::ifstream &InFile)
checks that the value is a double, returns true for success
Definition: Utilities.cpp:331
TTrainDataEntry::ExplicitDescription
bool ExplicitDescription
< headcode is the first train's headcode, rest are calculated from repeat information; ServiceReferen...
Definition: TrainUnit.h:215
TTrain::Straddle
TStraddle Straddle
the current Straddle value of the train (see TStraddle above)
Definition: TrainUnit.h:537
SignallerStop
@ SignallerStop
Definition: TrainUnit.h:57
TRailGraphics::CodeB
Graphics::TBitmap * CodeB
Definition: GraphicUnit.h:1001
TAllRoutes::DiagonalFouledByRouteOrTrain
bool DiagonalFouledByRouteOrTrain(int Caller, int HLoc, int VLoc, int DiagonalLinkNumber)
The track geometry allows diagonals to cross without occupying the same track element,...
Definition: TrackUnit.cpp:21519
TTrain::RevisedStoppedAtLoc
bool RevisedStoppedAtLoc() const
Definition: TrainUnit.h:545
Display
TDisplay * Display
The object pointer for the on-screen display, object created in InterfaceUnit.
Definition: DisplayUnit.cpp:54
TTrack::GetVLocMin
int GetVLocMin()
Definition: TrackUnit.h:911
TRailGraphics::Code3
Graphics::TBitmap * Code3
Definition: GraphicUnit.h:993
TUtilities::CheckFileBool
bool CheckFileBool(std::ifstream &InFile)
checks that the value is a bool returns true for success
Definition: Utilities.cpp:209
TTrain::MaximumPowerLimit
static const int MaximumPowerLimit
Watts (i.e. 100MW)
Definition: TrainUnit.h:321
TTrain::PlotStartPosition
void PlotStartPosition(int Caller)
Plots the train and sets up all relevant members for a new train when it is introduced into the railw...
Definition: TrainUnit.cpp:314
TTrack::TActiveTrackElementNameMapEntry
std::pair< AnsiString, int > TActiveTrackElementNameMapEntry
Definition: TrackUnit.h:710
TTrainController::SingleServiceOutput
void SingleServiceOutput(int Caller, int SSVectorNumber, TNumList MarkerList, TTrainDataVector &SingleServiceVector, std::ofstream &VecFile)
Outputs the single service vector for train direction analysis purposes in timetable conflict analysi...
Definition: TrainUnit.cpp:20710
TTrack::TInfrastructureFailureEntry::RepairTime
TDateTime RepairTime
Definition: TrackUnit.h:717
TTrainController::TContinuationAutoSigEntry::SecondDelay
double SecondDelay
Definition: TrainUnit.h:759
TTrain::UnplotTrainInZoomOutMode
void UnplotTrainInZoomOutMode(int Caller)
Unplot train from screen in zoomed-out mode.
Definition: TrainUnit.cpp:9470
TTimetableFormatType
TTimetableFormatType
Timetable entry types.
Definition: TrainUnit.h:69
TTrainController::TrainFailedWarning
bool TrainFailedWarning
Flags to enable the relevant warning graphics to flash at the left hand side of the screen.
Definition: TrainUnit.h:837
TTrain::NextTrainID
static int NextTrainID
< km/h
Definition: TrainUnit.h:330
TTrainController::TLocServiceTimes
Class used for timetable conflict file compilation.
Definition: TrainUnit.h:804
TTrainOperatingData::RunningEntry
TRunningEntry RunningEntry
Definition: TrainUnit.h:191
NotAShuttleLink
@ NotAShuttleLink
Definition: TrainUnit.h:86
TRailGraphics::gl91set
Graphics::TBitmap * gl91set
Definition: GraphicUnit.h:726
TRailGraphics::Code_y
Graphics::TBitmap * Code_y
Definition: GraphicUnit.h:988
TAllRoutes::CheckMapAndRoutes
void CheckMapAndRoutes(int Caller)
Diagnostic function - checks equivalence for each route between entries in PrefDirVector and those in...
Definition: TrackUnit.cpp:20615
TAllRoutes::GetRouteElementDataFromRoute2MultiMap
TRouteElementPair GetRouteElementDataFromRoute2MultiMap(int Caller, int HLoc, int VLoc, TRouteElementPair &SecondPair)
Retrieve up to two TRouteElementPair entries from Route2MultiMap at H & V, the first as a function re...
Definition: TrackUnit.cpp:20571
TTrain::CumulativeDelayedRandMinsOneTrain
double CumulativeDelayedRandMinsOneTrain
the running total of all random delays including knock-on delays for a single train,...
Definition: TrainUnit.h:455
TTrainController::TrainAdded
bool TrainAdded
true when a train has been added by a split (occurs outside the normal train introduction process)
Definition: TrainUnit.h:841
TActionVectorEntry::LocationName
AnsiString LocationName
Definition: TrainUnit.h:125
TTrainFormattedInformation::OneCompleteFormattedTrainVector
TOneCompleteFormattedTrainVector OneCompleteFormattedTrainVector
Definition: TrainUnit.h:288
ShuttleFinishedRemainingHere
@ ShuttleFinishedRemainingHere
Definition: TrainUnit.h:44
FailUnexpectedBuffers
@ FailUnexpectedBuffers
Definition: TrainUnit.h:42
TTrackElement::TrainIDOnBridgeOrFailedPointOrigSpeedLimit01
int TrainIDOnBridgeOrFailedPointOrigSpeedLimit01
Definition: TrackUnit.h:155
TRailGraphics::TempBackground
Graphics::TBitmap * TempBackground
Definition: GraphicUnit.h:909
TTrackElement::StationEntryStopLinkPos4
int StationEntryStopLinkPos4
Used for track at platforms ( 1 & 2) and non-station named locations (1 - 4) to mark the train front ...
Definition: TrackUnit.h:153
TTrain::TimeTimeLocArrived
bool TimeTimeLocArrived
indicates whether has arrived (true) or not when ActionVectorEntryPtr->FormatType == TimeTimeLoc
Definition: TrainUnit.h:343
TActionType
TActionType
Used in LogAction when reporting a train action to the performance log & file.
Definition: TrainUnit.h:54
TTrainController::TContinuationAutoSigEntry::ThirdDelay
double ThirdDelay
Delays in seconds before consecutive signal changes - these correspond to the times taken for trains ...
Definition: TrainUnit.h:759
TTrain::LeadEntryPos
int LeadEntryPos
Definition: TrainUnit.h:377
TTrainController::LateArrivals
int LateArrivals
Definition: TrainUnit.h:880
TRailGraphics::CodeZ
Graphics::TBitmap * CodeZ
Definition: GraphicUnit.h:1025
FailMissedCMS
@ FailMissedCMS
Definition: TrainUnit.h:45
NoShuttleLink
@ NoShuttleLink
Definition: TrainUnit.h:86
TTrainController::ReplotTrains
void ReplotTrains(int Caller, TDisplay *Disp)
plot all trains on the display
Definition: TrainUnit.cpp:10521
TTrainController::CheckShuttleServiceIntegrity
bool CheckShuttleServiceIntegrity(int Caller, TTrainDataEntry *TDEntryPtr, bool GiveMessages)
Check that each shuttle service ends either in Fns or Fxx-sh (though a single service can't end in Fx...
Definition: TrainUnit.cpp:16838
TTrain::FloatingTimetableString
AnsiString FloatingTimetableString(int Caller, TActionVectorEntry *Ptr)
Used in the floating window to display the timetable.
Definition: TrainUnit.cpp:8188
TAllRoutes::GetModifiableRouteAt
TOneRoute & GetModifiableRouteAt(int Caller, int At)
Returns a modifiable reference to the route at AllRoutesVector position 'At', after performing range ...
Definition: TrackUnit.cpp:19844
TTrain::NewShuttleFromNonRepeatService
void NewShuttleFromNonRepeatService(int Caller, bool NoLogFlag)
Carry out the actions needed when a new shuttle service is created from a non-repeating (F-nshs) serv...
Definition: TrainUnit.cpp:7033
TTrain::AValue
double AValue
< only true when a train has become a follow-on service early and the follow-on service normally pass...
Definition: TrainUnit.h:427
FailUnexpectedExitRailway
@ FailUnexpectedExitRailway
Definition: TrainUnit.h:42
TTrain::FrontTrainSplit
void FrontTrainSplit(int Caller)
Carry out the actions needed when a train is to split from the front.
Definition: TrainUnit.cpp:6151
TTrainController::WithinTimeRange
bool WithinTimeRange(int Caller, AnsiString Time1, AnsiString Time2, int MinuteRange)
check whether the two times are within the range in minutes specified and return true if so....
Definition: TrainUnit.cpp:20854
TTrainController::WriteTrainsToImage
void WriteTrainsToImage(int Caller, Graphics::TBitmap *Bitmap)
Called by TInterface::SaveOperatingImage1Click) to write all trains to the image file.
Definition: TrainUnit.cpp:10539
TDisplay::DisplayOffsetH
static int DisplayOffsetH
the horizontal offset of the displayed screen (as viewpoint moves to the right [railway moves left] t...
Definition: DisplayUnit.h:77
TActionVectorEntry::LinkedTrainEntryPtr
TTrainDataEntry * LinkedTrainEntryPtr
link pointer for use between fsp/rsp & Sfs; Fjo & jbo; Fns & Sns; & all shuttle to shuttle links
Definition: TrainUnit.h:149
TTrainController::AtLocSuccessor
bool AtLocSuccessor(const TActionVectorEntry &AVEntry)
A shorthand function that returns true if the successor to a given timetable action command should be...
Definition: TrainUnit.cpp:15396
TTrain::ZeroPowerNoJoinedByMessage
bool ZeroPowerNoJoinedByMessage
Definition: TrainUnit.h:352
TTrainController::SaveSessionContinuationAutoSigEntries
void SaveSessionContinuationAutoSigEntries(int Caller, std::ofstream &SessionFile)
save ContinuationAutoSigEntries to a session file
Definition: TrainUnit.cpp:17542
TTrainOperatingData::TrainID
int TrainID
Definition: TrainUnit.h:189
TUtilities::MaxRandomRepairTime
int MaxRandomRepairTime
Definition: Utilities.h:71
TDisplay::GetOutputLog3
TLabel * GetOutputLog3()
Definition: DisplayUnit.h:155
NoLocation
@ NoLocation
Definition: TrainUnit.h:76
TTrainController::CheckCrossReferencesAndSetData
bool CheckCrossReferencesAndSetData(int Caller, AnsiString SoughtHeadCode, AnsiString SeekingHeadCode, bool Shuttle, bool SetDataAndCheckLocations, bool GiveMessages)
A timetable validation function where all service cross references are checked for validity and set p...
Definition: TrainUnit.cpp:15517
TRailGraphics::bmTransparentBgnd
Graphics::TBitmap * bmTransparentBgnd
Definition: GraphicUnit.h:936
TTrainController::MinsToAnsiTime
AnsiString MinsToAnsiTime(int Input)
converts an integer minute value to string "HH:MM" added at v2.15.0
Definition: TrainUnit.cpp:16923
TTrain::FirstHalfMove
bool FirstHalfMove
true when the train is on the first half of an element when it displays as fully on two elements....
Definition: TrainUnit.h:399
FailLocTooShort
@ FailLocTooShort
Definition: TrainUnit.h:41
TTrainDataEntry::Mass
int Mass
in kg
Definition: TrainUnit.h:223
NoMode
@ NoMode
Definition: TrainUnit.h:64
TTrainController::LatePasses
int LatePasses
Definition: TrainUnit.h:882
TTrainController::SplitRepeat
bool SplitRepeat(int Caller, AnsiString OneEntry, int &RearStartOrRepeatMins, int &FrontStartOrRepeatDigits, int &RepeatNumber, bool GiveMessages)
Parse a timetable repeat entry, return true for success.
Definition: TrainUnit.cpp:13261
TFixedTrackPiece::TrackType
TTrackType TrackType
the type of track element
Definition: TrackUnit.h:100
TTrainController::TLocServiceTimes::ArrTime
AnsiString ArrTime
Definition: TrainUnit.h:808
TTrainController::SSHigh
bool SSHigh
Definition: TrainUnit.h:851
TimeLoc
@ TimeLoc
Definition: TrainUnit.h:70
TActionVectorIterator
TActionVector::iterator TActionVectorIterator
iterator
Definition: TrainUnit.h:180
TPrefDirElement
Basic preferred direction or route element - track element with additional members.
Definition: TrackUnit.h:201
TTrainController::AvHoursIntValue
int AvHoursIntValue
Input in MTBFEditBox in timetable hours, min value is 1 and max is 10,000. Here because performance f...
Definition: TrainUnit.h:903
TTrainController::CrashedTrains
int CrashedTrains
Definition: TrainUnit.h:874
TTrainController::TTEditPanelVisible
bool TTEditPanelVisible
new at v2.6.0 so potential error message only shows in TTEdit mode
Definition: TrainUnit.h:849
TDisplay::WarningLog
void WarningLog(int Caller, AnsiString Statement)
Display warning message Statement in the bottom left hand warning position and scroll other messages ...
Definition: DisplayUnit.cpp:524
TTrain::StoppedAtSignal
bool StoppedAtSignal
Definition: TrainUnit.h:504
TTrainController::GetControllerTrainTime
TDateTime GetControllerTrainTime(int Caller, TDateTime Time, int RepeatNumber, int IncrementalMinutes)
Get the interval between repeats.
Definition: TrainUnit.cpp:10923
TRailGraphics::Code_u
Graphics::TBitmap * Code_u
Definition: GraphicUnit.h:984
TTrackElement::Attribute
int Attribute
special variable used only for points, signals & level crossings, ignored otherwise; points 0=set to ...
Definition: TrackUnit.h:143
TStraddle
TStraddle
Defines the train position with respect to the track elements; three consecutive elements are Lead (f...
Definition: TrainUnit.h:303
FailEntryRouteSetAgainst
@ FailEntryRouteSetAgainst
Definition: TrainUnit.h:45
TTrain::TrainSkippedEvents
int TrainSkippedEvents
stores the pointer increment from the current action in ActionVector for skipped actions when a depar...
Definition: TrainUnit.h:387
TTrainController::THCandTrainPosParam
std::pair< AnsiString, int > THCandTrainPosParam
Definition: TrainUnit.h:820
TExitInfo::ServiceReference
AnsiString ServiceReference
Definition: TrainUnit.h:109
TTrainDataVector
std::vector< TTrainDataEntry > TTrainDataVector
vector class for containing the whole timetable - one entry per timetable service entry (the object i...
Definition: TrainUnit.h:246
TTrainController::GetRepeatTime
TDateTime GetRepeatTime(int Caller, TDateTime BasicTime, int RepeatNumber, int IncMinutes)
Return the repeating service time.
Definition: TrainUnit.cpp:16511
clCrashedBackground
#define clCrashedBackground
Definition: GraphicUnit.h:294
TTrack::IsLCAtHV
bool IsLCAtHV(int Caller, int HLoc, int VLoc)
True if a level crossing is found at H & V.
Definition: TrackUnit.cpp:7596
TTrain::OpTimeToAct
float OpTimeToAct
in minutes: new at v2.2.0 for operator time to act panel. Calculated in UpdateTrain,...
Definition: TrainUnit.h:463
TTrainController::TTClockTime
TDateTime TTClockTime
the time indicated by the timetable clock
Definition: TrainUnit.h:746
TAllRoutes::TLockedRouteClass::LockStartTime
TDateTime LockStartTime
the timetable time at which the route is locked, to start the 2 minute clock
Definition: TrackUnit.h:1675
TActionVectorEntry::SequenceType
TTimetableSequenceType SequenceType
indicates where in the sequence of codes the action lies
Definition: TrainUnit.h:145
TTrain::RearStartExitPos
int RearStartExitPos
the LinkPos value for the rear starting element (i.e. links to the front starting element)
Definition: TrainUnit.h:369
TTrainController::TLocServiceTimes::FrhMarker
AnsiString FrhMarker
Definition: TrainUnit.h:810
TRailGraphics::Code_o
Graphics::TBitmap * Code_o
Definition: GraphicUnit.h:978
TRailGraphics::CodeV
Graphics::TBitmap * CodeV
Definition: GraphicUnit.h:1021
TTrainController::TLocServiceTimes::ServiceAndRepeatNum
AnsiString ServiceAndRepeatNum
Definition: TrainUnit.h:806
TTrain::SetTrainElementID
void SetTrainElementID(int Caller, unsigned int TrackVectorPosition, int EntryPos)
When a train moves onto an element that element has its TrainIDOnElement value set to the TrainID val...
Definition: TrainUnit.cpp:3650
TActionVectorEntry::NonRepeatingShuttleLinkHeadCode
AnsiString NonRepeatingShuttleLinkHeadCode
Definition: TrainUnit.h:125
TActionVectorEntry::ShuttleLinkType
TTimetableShuttleLinkType ShuttleLinkType
indicates whether or not the action relates to a shuttle service link
Definition: TrainUnit.h:147
TTrain::TimetableFinished
bool TimetableFinished
set when there are no more timetable actions
Definition: TrainUnit.h:421
TUtilities::CallLog
std::deque< AnsiString > CallLog
call stack store, saved to the errorlog for diagnostic purposes
Definition: Utilities.h:105
TRailGraphics::CodeN
Graphics::TBitmap * CodeN
Definition: GraphicUnit.h:1013
TActionVectorEntry::Command
AnsiString Command
Definition: TrainUnit.h:125
TTrain::RepeatShuttleOrRemainHere
void RepeatShuttleOrRemainHere(int Caller, bool NoLogFlag)
Carry out the actions needed to create either a new shuttle service or (if all repeats have finished)...
Definition: TrainUnit.cpp:7087
TTrain::CallingOnAllowed
bool CallingOnAllowed(int Caller)
True if the train can be called on at its current position - see detail in .cpp file.
Definition: TrainUnit.cpp:5372
TTrainController::TContinuationTrainExpectationEntry::HeadCode
AnsiString HeadCode
< service description
Definition: TrainUnit.h:779
TTrackElement::Length23
int Length23
Definition: TrackUnit.h:151
TTrain::SignallerStoppingFlag
bool SignallerStoppingFlag
set when the signaller stop command has been given
Definition: TrainUnit.h:413
TTrainController::NotStartedTrainLateArr
int NotStartedTrainLateArr
total number of arrivals & departures for trains that haven't started yet for locations not reached y...
Definition: TrainUnit.h:899
TUtilities::LoadFileBool
bool LoadFileBool(std::ifstream &InFile)
loads a bool value from the file
Definition: Utilities.cpp:145
TTrainController::SigSLow
bool SigSLow
Definition: TrainUnit.h:851
TRailGraphics::Code_m
Graphics::TBitmap * Code_m
Definition: GraphicUnit.h:976
TTrack::AnyLinkedBarrierDownVectorManual
bool AnyLinkedBarrierDownVectorManual(int Caller, int HLoc, int VLoc, int &BDVectorPos)
Checks BarrierDownVector and returns true if there is one that is linked to the LC at H & V positions...
Definition: TrackUnit.cpp:6620
TRailGraphics::gl92set
Graphics::TBitmap * gl92set
Definition: GraphicUnit.h:728
SNSShuttle
@ SNSShuttle
Definition: TrainUnit.h:70
RemoveTrain
@ RemoveTrain
Definition: TrainUnit.h:56
TTrainController::TLocServiceTimes::DepTime
AnsiString DepTime
Definition: TrainUnit.h:809
TTrainController::SetWarningFlags
void SetWarningFlags(int Caller)
This sets all the warning flags (CrashWarning, DerailWarning etc) to their required states after a se...
Definition: TrainUnit.cpp:22019
TDisplay::PlotSmallOutput
void PlotSmallOutput(int Caller, int HPos, int VPos, Graphics::TBitmap *PlotItem)
Plot small (4x4) graphic PlotItem on the zoomed-out display at HPos & Vpos.
Definition: DisplayUnit.cpp:115
THVShortPair
std::pair< int, int > THVShortPair
Definition: InterfaceUnit.h:82
TTrainController::TContinuationTrainExpectationEntry
Class that stores data for trains expected at continuation entries (kept in a multimap - see below),...
Definition: TrainUnit.h:775
TTrainController::TContinuationTrainExpectationEntry::FixedDescription
AnsiString FixedDescription
Definition: TrainUnit.h:777
SNSNonRepeatFromShuttle
@ SNSNonRepeatFromShuttle
Definition: TrainUnit.h:70
TTrainController::TotEarlyPassMins
float TotEarlyPassMins
Definition: TrainUnit.h:866
TRailGraphics::CodeH
Graphics::TBitmap * CodeH
Definition: GraphicUnit.h:1007
TTrainController::OpTimeToActMultiMap
TOpTimeToActMultiMap OpTimeToActMultiMap
added v2.2.0 for Op time to act display
Definition: TrainUnit.h:917
TTrainController::TContinuationAutoSigEntry::AccessNumber
int AccessNumber
the number of times the signal changing function has been accessed - starts at 0 and increments after...
Definition: TrainUnit.h:761
TActionVectorEntry::Reminder
unsigned int Reminder
Definition: TrainUnit.h:153
TActionVectorEntry::SignallerControl
bool SignallerControl
< string values for timetabled event entries, null on creation //NewMaxSpeed added at v2....
Definition: TrainUnit.h:129
TTrain::GetTrainTime
TDateTime GetTrainTime(int Caller, TDateTime Time)
Returns the timetable action time corresponding to 'Time' for this train, i.e. it adjusts the time va...
Definition: TrainUnit.cpp:5652
TTrain::BrakeRate
double BrakeRate
the current train brake rate
Definition: TrainUnit.h:445
TUtilities::LastDelayTTClockTime
double LastDelayTTClockTime
Clock time at which the latest delay for any train occurred. Used to prevent new delays within 5 minu...
Definition: Utilities.h:85
TTrackElement::SpeedLimit23
int SpeedLimit23
Element lengths and speed limits, ...01 is for the track with link positions [0] and [1],...
Definition: TrackUnit.h:151
TTrainController::TContinuationAutoSigEntry::PassoutTime
TDateTime PassoutTime
the timetable clock time at which the train exits from the continuation
Definition: TrainUnit.h:765
TTrain::FinishJoinLogSent
bool FinishJoinLogSent
Definition: TrainUnit.h:347
TTrack::GapFlashFlag
bool GapFlashFlag
true when a pair of connected gaps is flashing
Definition: TrackUnit.h:757
TRailGraphics::gl95set
Graphics::TBitmap * gl95set
Definition: GraphicUnit.h:732
TrainUnit.h
PassTime
@ PassTime
Definition: TrainUnit.h:71
TTrainController::IncorrectExits
int IncorrectExits
Definition: TrainUnit.h:879
TTrain::ContinuationExit
bool ContinuationExit(int Caller, int Element, int Exitpos) const
True if Element is a continuation and Exitpos is the continuation end.
Definition: TrainUnit.cpp:3574
TFixedTrackPiece::Link
int Link[4]
Track connection link values, max. of 4, unused = -1, top lh diag = 1, top = 2, top rh diag = 3,...
Definition: TrackUnit.h:90
TTrain::UpdateTrain
void UpdateTrain(int Caller)
Major function called at each clock tick for each train & handles all train movement & associated act...
Definition: TrainUnit.cpp:685
TRailGraphics::LockedRouteCancelPtr
Graphics::TBitmap * LockedRouteCancelPtr[10]
for locked route cancel graphic, 1 for each of 8 links, 0 & 5 included as for direction
Definition: GraphicUnit.h:1077
TRailGraphics::Code8
Graphics::TBitmap * Code8
Definition: GraphicUnit.h:998
TTrainController::SameDirection
bool SameDirection(int Caller, AnsiString Ref1, AnsiString Ref2, AnsiString Time1, AnsiString Time2, int RepeatNum1, int RepeatNum2, TServiceCallingLocsList List1, TServiceCallingLocsList List2, AnsiString Location, bool Arrival)
Determines whether two services are running in the same direction when they arrive or depart from Loc...
Definition: TrainUnit.cpp:21138
TTrain::TrainToBeJoinedByIsAdjacent
bool TrainToBeJoinedByIsAdjacent(int Caller, TTrain *&TrainToBeJoinedBy)
True for a train waiting to be joined when the joining train is adjacent.
Definition: TrainUnit.cpp:7004
TTrackElement::TrainIDOnBridgeOrFailedPointOrigSpeedLimit23
int TrainIDOnBridgeOrFailedPointOrigSpeedLimit23
Definition: TrackUnit.h:155
TTrain::SetTrainMovementValues
void SetTrainMovementValues(int Caller, int TrackVectorPosition, int EntryPos)
Calculates train speeds and times for the element that the train is about to enter....
Definition: TrainUnit.cpp:4070
TActionVectorEntry::Warning
bool Warning
if set triggers an alert in the warning and perf log panels when the action is reached
Definition: TrainUnit.h:131
TTrain::PickUpBackgroundBitmap
void PickUpBackgroundBitmap(int Caller, int HOffset, int VOffset, int Element, int EntryPos, Graphics::TBitmap *GraphicPtr) const
Store the background bitmap pointer (BackgroundPtr - see above) prior to being overwritten by the tra...
Definition: TrainUnit.cpp:3162
clStoppedTrainInFront
#define clStoppedTrainInFront
Definition: GraphicUnit.h:303
TDisplay::GetOutputLog10
TLabel * GetOutputLog10()
Definition: DisplayUnit.h:190
TTrainDataEntry::ServiceReference
AnsiString ServiceReference
Definition: TrainUnit.h:213
TTrain::SignallerChangeTrainDirection
void SignallerChangeTrainDirection(int Caller)
Unplots & replots train, which checks for facing signal and sets StoppedAtSignal if req'd.
Definition: TrainUnit.cpp:7367
TTrain::ZeroPowerNoRearSplitMessage
bool ZeroPowerNoRearSplitMessage
Definition: TrainUnit.h:350
FailMissedChangeDirection
@ FailMissedChangeDirection
Definition: TrainUnit.h:43
NotSet
@ NotSet
Definition: TrackUnit.h:76
Repeat
@ Repeat
Definition: TrainUnit.h:71
TRailGraphics::Code_c
Graphics::TBitmap * Code_c
Definition: GraphicUnit.h:966
TTrainController::StripExcessFromHeadCode
void StripExcessFromHeadCode(int Caller, AnsiString &HeadCode)
change an extended headcode to an ordinary 4 character headcode
Definition: TrainUnit.cpp:15405
FinishSequence
@ FinishSequence
Definition: TrainUnit.h:81
TTrack::TIMPair
std::pair< unsigned int, unsigned int > TIMPair
TrackElement pair type used for inactive elements, values are vector positions.
Definition: TrackUnit.h:678
TUtilities::Clock2Stopped
bool Clock2Stopped
when true the main loop - Interface->ClockTimer2 - is stopped
Definition: Utilities.h:75
TTrack::OtherTrainOnTrack
bool OtherTrainOnTrack(int Caller, int TrackPos, int LinkPos, int OwnTrainID)
True if another train on LinkPos track of element at TrackPos, whether bridge or not,...
Definition: TrackUnit.cpp:11967
TTrackElement::GroundSignal
@ GroundSignal
Definition: TrackUnit.h:161
LevelCrossing
@ LevelCrossing
Definition: TrackUnit.h:67
TDisplay::GetOutputLog2
TLabel * GetOutputLog2()
Definition: DisplayUnit.h:150
TTrain::NotInService
bool NotInService
Definition: TrainUnit.h:505
TRailGraphics::CodeA
Graphics::TBitmap * CodeA
Definition: GraphicUnit.h:1000
TTrack::ActiveTrackElementNameMapCompiledFlag
bool ActiveTrackElementNameMapCompiledFlag
indicates that the ActiveTrackElementNameMap has been compiled
Definition: TrackUnit.h:751
TTrainController::LocServiceTimesLocationSort
bool LocServiceTimesLocationSort(TLocServiceTimes i, TLocServiceTimes j)
< Colour used to replace long serv. ref. text colour when removed - can't be transparent or text rema...
Definition: TrainUnit.h:937
TOneTrainFormattedEntry
A single train timetable action for use in a formatted timetable.
Definition: TrainUnit.h:254
TTrain::RearTrainSplit
void RearTrainSplit(int Caller)
Carry out the actions needed when a train is to split from the rear.
Definition: TrainUnit.cpp:6311
TAllRoutes::TLockedRouteClass::RearTrackVectorPosition
unsigned int RearTrackVectorPosition
the TrackVector position of the rearmost element selected for truncation (this will be truncated)
Definition: TrackUnit.h:1669
TTrainController::ConsolidateSARNTArrDep
AnsiString ConsolidateSARNTArrDep(int Caller, const AnsiString Input, int &NumTrainsAtLoc, AnsiString Location, bool Arrival, bool &AnalysisError, int &MaxNumberOfSameDirections)
Removes duplicates from and sorts ServiceAndRepeatNumTotal into alphabetical order for arrivals (bool...
Definition: TrainUnit.cpp:20880
Track
TTrack * Track
the object pointer, object created in InterfaceUnit
Definition: TrackUnit.cpp:53
TTrackElement::Conn
int Conn[4]
Connecting element position in TrackVector, set to -1 if no connecting link or if track not linked.
Definition: TrackUnit.h:145
Signaller
@ Signaller
Definition: TrainUnit.h:64
TTrain::TrainCrashedInto
int TrainCrashedInto
the TrainID of the train that this train has crashed into, recorded so that train can be marked and d...
Definition: TrainUnit.h:517
TRailGraphics::smYellow
Graphics::TBitmap * smYellow
Definition: GraphicUnit.h:907
RailGraphics
TRailGraphics * RailGraphics
the object pointer, object created in InterfaceUnit
Definition: GraphicUnit.cpp:50
TTrainOperatingData
Data for a specific train for use during operation.
Definition: TrainUnit.h:187
FailCreatePoints
@ FailCreatePoints
Definition: TrainUnit.h:41
TRailGraphics::smBlack
Graphics::TBitmap * smBlack
Definition: GraphicUnit.h:895
TTrain::TreatPassAsTimeLocDeparture
bool TreatPassAsTimeLocDeparture
< indicates failure
Definition: TrainUnit.h:425
Bridge
@ Bridge
Definition: TrackUnit.h:66
TRailGraphics::Code_p
Graphics::TBitmap * Code_p
Definition: GraphicUnit.h:979
TTrainFormattedInformation::NumberOfTrains
int NumberOfTrains
number of repeats + 1
Definition: TrainUnit.h:286
TTrain::LagExitPos
int LagExitPos
TrackVector positions, & entry & exit connection positions for the elements that the train occupies.
Definition: TrainUnit.h:377
TTimetableSequenceType
TTimetableSequenceType
Definition: TrainUnit.h:80
TTrain::TimetableMaxRunningSpeed
double TimetableMaxRunningSpeed
the maximum train running speed when in timetable mode (see int SignallerMaxSpeed for signaller contr...
Definition: TrainUnit.h:437
SequTypeForRepeatEntry
@ SequTypeForRepeatEntry
Definition: TrainUnit.h:81
TRailGraphics::bmName
Graphics::TBitmap * bmName
Definition: GraphicUnit.h:528
clB0G0R0
#define clB0G0R0
Definition: GraphicUnit.h:37
Buffers
@ Buffers
Definition: TrackUnit.h:66
TActionVectorEntry::RearStartOrRepeatMins
int RearStartOrRepeatMins
Definition: TrainUnit.h:135
clFrontCodeTimetable
#define clFrontCodeTimetable
Definition: GraphicUnit.h:297
TTrack::OneNonStationLongEnoughForSplit
bool OneNonStationLongEnoughForSplit(int Caller, AnsiString LocationName)
As below but here allow points & crossovers.
Definition: TrackUnit.cpp:11391
TActionVectorEntry::OtherHeadCode
AnsiString OtherHeadCode
Definition: TrainUnit.h:125